diff options
Diffstat (limited to 'test')
590 files changed, 0 insertions, 134865 deletions
diff --git a/test/BUILD b/test/BUILD deleted file mode 100644 index 34b950644..000000000 --- a/test/BUILD +++ /dev/null @@ -1 +0,0 @@ -package(licenses = ["notice"]) diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 15b0f4c33..000000000 --- a/test/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Tests - -The tests defined under this path are verifying functionality beyond what unit -tests can cover, e.g. integration and end to end tests. Due to their nature, -they may need extra setup in the test machine and extra configuration to run. - -- **syscalls**: system call tests use a local runner, and do not require - additional configuration in the machine. -- **integration:** defines integration tests that uses `docker run` to test - functionality. -- **image:** basic end to end test for popular images. These require the same - setup as integration tests. -- **root:** tests that require to be run as root. These require the same setup - as integration tests. -- **util:** utilities library to support the tests. - -For the above noted cases, the relevant runtime must be installed via `runsc -install` before running. Just note that they require specific configuration to -work. This is handled automatically by the test scripts in the `scripts` -directory and they can be used to run tests locally on your machine. They are -also used to run these tests in `kokoro`. - -**Example:** - -To run image and integration tests, run: - -`make docker-tests` - -To run root tests, run: - -`make root-tests` - -There are a few other interesting variations for image and integration tests: - -* overlay: sets writable overlay inside the sentry -* hostnet: configures host network pass-thru, instead of netstack -* kvm: runsc the test using the KVM platform, instead of ptrace - -The test will build runsc, configure it with your local docker, restart -`dockerd`, and run tests. The location for runsc logs is printed to the output. diff --git a/test/benchmarks/BUILD b/test/benchmarks/BUILD deleted file mode 100644 index faf310676..000000000 --- a/test/benchmarks/BUILD +++ /dev/null @@ -1,11 +0,0 @@ -load("//tools:defs.bzl", "bzl_library") - -package(licenses = ["notice"]) - -bzl_library( - name = "defs_bzl", - srcs = ["defs.bzl"], - visibility = [ - "//:sandbox", - ], -) diff --git a/test/benchmarks/README.md b/test/benchmarks/README.md deleted file mode 100644 index c745a5b1e..000000000 --- a/test/benchmarks/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Benchmark tools - -This package and subpackages are for running macro benchmarks on `runsc`. They -are meant to replace the previous //benchmarks benchmark-tools written in -python. - -Benchmarks are meant to look like regular golang benchmarks using the testing.B -library. - -## Setup - -To run benchmarks you will need: - -* Docker installed (17.09.0 or greater). - -## Running benchmarks - -To run, use the Makefile: - -- Install runsc as a runtime: `make dev` - - The above command will place several configurations of runsc in your - /etc/docker/daemon.json file. Choose one without the debug option set. -- Run your benchmark: `make run-benchmark - RUNTIME=[RUNTIME_FROM_DAEMON.JSON/runc] - BENCHMARKS_TARGETS=//path/to/target"` -- Additionally, you can benchmark several platforms in one command: - -``` -make benchmark-platforms BENCHMARKS_PLATFORMS=ptrace,kvm \ -BENCHMARKS_TARGET=//path/to/target" -``` - -The above command will install runtimes/run benchmarks on ptrace and kvm as well -as run the benchmark on native runc. - -Benchmarks are run with root as some benchmarks require root privileges to do -things like drop caches. - -## Writing benchmarks - -Benchmarks consist of docker images as Dockerfiles and golang testing.B -benchmarks. - -### Dockerfiles: - -* Are stored at //images. -* New Dockerfiles go in an appropriately named directory at - `//images/benchmarks/my-cool-dockerfile`. -* Dockerfiles for benchmarks should: - * Use explicitly versioned packages. - * Don't use ENV and CMD statements. It is easy to add these in the API via - `dockerutil.RunOpts`. -* Note: A common pattern for getting access to a tmpfs mount is to copy files - there after container start. See: //test/benchmarks/build/bazel_test.go. You - can also make your own with `RunOpts.Mounts`. - -### testing.B packages - -In general, benchmarks should look like this: - -```golang -func BenchmarkMyCoolOne(b *testing.B) { - machine, err := harness.GetMachine() - // check err - defer machine.CleanUp() - - ctx := context.Background() - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) - - b.ResetTimer() - - // Respect b.N. - for i := 0; i < b.N; i++ { - out, err := container.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/my-cool-image", - Env: []string{"MY_VAR=awesome"}, - // other options...see dockerutil - }, "sh", "-c", "echo MY_VAR") - // check err... - b.StopTimer() - - // Do parsing and reporting outside of the timer. - number := parseMyMetric(out) - b.ReportMetric(number, "my-cool-custom-metric") - - b.StartTimer() - } -} - -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} -``` - -Some notes on the above: - -* Respect and linearly scale by `b.N` so that users can run a number of times - (--benchtime=10x) or for a time duration (--benchtime=1m). For many - benchmarks, this is just the runtime of the container under test. Sometimes - this is a parameter to the container itself. For Example, the httpd - benchmark (and most client server benchmarks) uses b.N as a parameter to the - Client container that specifies how many requests to make to the server. -* Use the `b.ReportMetric()` method to report custom metrics. -* Never turn off the timer (b.N), but set and reset it if useful for the - benchmark. There isn't a way to turn off default metrics in testing.B (B/op, - allocs/op, ns/op). -* Take a look at dockerutil at //pkg/test/dockerutil to see all methods - available from containers. The API is based on the "official" - [docker API for golang](https://pkg.go.dev/mod/github.com/docker/docker). -* `harness.GetMachine()` marks how many machines this tests needs. If you have - a client and server and to mark them as multiple machines, call - `harness.GetMachine()` twice. - -## Profiling - -For profiling, the runtime is required to have the `--profile` flag enabled. -This flag loosens seccomp filters so that the runtime can write profile data to -disk. This configuration is not recommended for production. - -To profile, simply run the `benchmark-platforms` command from above and profiles -will be in /tmp/profile. - -Or run with: `make run-benchmark RUNTIME=[RUNTIME_UNDER_TEST] -BENCHMARKS_TARGETS=//path/to/target` - -Profiles will be in /tmp/profile. Note: runtimes must have the `--profile` flag -set in /etc/docker/daemon.conf and profiling will not work on runc. diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD deleted file mode 100644 index 697ab5837..000000000 --- a/test/benchmarks/base/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -go_library( - name = "base", - testonly = 1, - srcs = [ - "base.go", - ], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - ], -) - -benchmark_test( - name = "startup_test", - size = "enormous", - srcs = ["startup_test.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/base", - "//test/benchmarks/harness", - ], -) - -benchmark_test( - name = "size_test", - size = "enormous", - srcs = ["size_test.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/base", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - ], -) - -benchmark_test( - name = "sysbench_test", - size = "enormous", - srcs = ["sysbench_test.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - ], -) diff --git a/test/benchmarks/base/base.go b/test/benchmarks/base/base.go deleted file mode 100644 index 979564af9..000000000 --- a/test/benchmarks/base/base.go +++ /dev/null @@ -1,98 +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 base holds utility methods common to the base tests. -package base - -import ( - "context" - "net" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -// ServerArgs wraps args for startServers and runServerWorkload. -type ServerArgs struct { - Machine harness.Machine - Port int - RunOpts dockerutil.RunOpts - Cmd []string -} - -// StartServers starts b.N containers defined by 'runOpts' and 'cmd' and uses -// 'machine' to check that each is up. -func StartServers(ctx context.Context, b *testing.B, args ServerArgs) []*dockerutil.Container { - b.Helper() - servers := make([]*dockerutil.Container, 0, b.N) - - // Create N servers and wait until each of them is serving. - for i := 0; i < b.N; i++ { - server := args.Machine.GetContainer(ctx, b) - servers = append(servers, server) - if err := server.Spawn(ctx, args.RunOpts, args.Cmd...); err != nil { - CleanUpContainers(ctx, servers) - b.Fatalf("failed to spawn node instance: %v", err) - } - - // Get the container IP. - servingIP, err := server.FindIP(ctx, false) - if err != nil { - CleanUpContainers(ctx, servers) - b.Fatalf("failed to get ip from server: %v", err) - } - - // Wait until the server is up. - if err := harness.WaitUntilServing(ctx, args.Machine, servingIP, args.Port); err != nil { - CleanUpContainers(ctx, servers) - b.Fatalf("failed to wait for serving") - } - } - return servers -} - -// CleanUpContainers cleans up a slice of containers. -func CleanUpContainers(ctx context.Context, containers []*dockerutil.Container) { - for _, c := range containers { - if c != nil { - c.CleanUp(ctx) - } - } -} - -// RedisInstance returns a Redis container and its reachable IP. -func RedisInstance(ctx context.Context, b *testing.B, machine harness.Machine) (*dockerutil.Container, net.IP) { - b.Helper() - // Spawn a redis instance for the app to use. - redis := machine.GetNativeContainer(ctx, b) - if err := redis.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }); err != nil { - redis.CleanUp(ctx) - b.Fatalf("failed to spwan redis instance: %v", err) - } - - if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil { - redis.CleanUp(ctx) - b.Fatalf("failed to start redis server: %v %s", err, out) - } - redisIP, err := redis.FindIP(ctx, false) - if err != nil { - redis.CleanUp(ctx) - b.Fatalf("failed to get IP from redis instance: %v", err) - } - return redis, redisIP -} diff --git a/test/benchmarks/base/size_test.go b/test/benchmarks/base/size_test.go deleted file mode 100644 index 452926e5f..000000000 --- a/test/benchmarks/base/size_test.go +++ /dev/null @@ -1,181 +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 size_test - -import ( - "context" - "os" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/base" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// BenchmarkSizeEmpty creates N empty containers and reads memory usage from -// /proc/meminfo. -func BenchmarkSizeEmpty(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer machine.CleanUp() - meminfo := tools.Meminfo{} - ctx := context.Background() - containers := make([]*dockerutil.Container, 0, b.N) - - // DropCaches before the test. - harness.DropCaches(machine) - - // Check available memory on 'machine'. - cmd, args := meminfo.MakeCmd() - before, err := machine.RunCommand(cmd, args...) - if err != nil { - b.Fatalf("failed to get meminfo: %v", err) - } - - // Make N containers. - for i := 0; i < b.N; i++ { - container := machine.GetContainer(ctx, b) - containers = append(containers, container) - if err := container.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/alpine", - }, "sh", "-c", "echo Hello && sleep 1000"); err != nil { - base.CleanUpContainers(ctx, containers) - b.Fatalf("failed to run container: %v", err) - } - if _, err := container.WaitForOutputSubmatch(ctx, "Hello", 5*time.Second); err != nil { - base.CleanUpContainers(ctx, containers) - b.Fatalf("failed to read container output: %v", err) - } - } - - // Drop caches again before second measurement. - harness.DropCaches(machine) - - // Check available memory after containers are up. - after, err := machine.RunCommand(cmd, args...) - base.CleanUpContainers(ctx, containers) - if err != nil { - b.Fatalf("failed to get meminfo: %v", err) - } - meminfo.Report(b, before, after) -} - -// BenchmarkSizeNginx starts N containers running Nginx, checks that they're -// serving, and checks memory used based on /proc/meminfo. -func BenchmarkSizeNginx(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer machine.CleanUp() - - // DropCaches for the first measurement. - harness.DropCaches(machine) - - // Measure MemAvailable before creating containers. - meminfo := tools.Meminfo{} - cmd, args := meminfo.MakeCmd() - before, err := machine.RunCommand(cmd, args...) - if err != nil { - b.Fatalf("failed to run meminfo command: %v", err) - } - - // Make N Nginx containers. - ctx := context.Background() - runOpts := dockerutil.RunOpts{ - Image: "benchmarks/nginx", - } - const port = 80 - servers := base.StartServers(ctx, b, - base.ServerArgs{ - Machine: machine, - Port: port, - RunOpts: runOpts, - Cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"}, - }) - defer base.CleanUpContainers(ctx, servers) - - // DropCaches after servers are created. - harness.DropCaches(machine) - // Take after measurement. - after, err := machine.RunCommand(cmd, args...) - if err != nil { - b.Fatalf("failed to run meminfo command: %v", err) - } - meminfo.Report(b, before, after) -} - -// BenchmarkSizeNode starts N containers running a Node app, checks that -// they're serving, and checks memory used based on /proc/meminfo. -func BenchmarkSizeNode(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer machine.CleanUp() - - // Make a redis instance for Node to connect. - ctx := context.Background() - redis, redisIP := base.RedisInstance(ctx, b, machine) - defer redis.CleanUp(ctx) - - // DropCaches after redis is created. - harness.DropCaches(machine) - - // Take before measurement. - meminfo := tools.Meminfo{} - cmd, args := meminfo.MakeCmd() - before, err := machine.RunCommand(cmd, args...) - if err != nil { - b.Fatalf("failed to run meminfo commend: %v", err) - } - - // Create N Node servers. - runOpts := dockerutil.RunOpts{ - Image: "benchmarks/node", - WorkDir: "/usr/src/app", - Links: []string{redis.MakeLink("redis")}, - } - nodeCmd := []string{"node", "index.js", redisIP.String()} - const port = 8080 - servers := base.StartServers(ctx, b, - base.ServerArgs{ - Machine: machine, - Port: port, - RunOpts: runOpts, - Cmd: nodeCmd, - }) - defer base.CleanUpContainers(ctx, servers) - - // DropCaches after servers are created. - harness.DropCaches(machine) - // Take after measurement. - cmd, args = meminfo.MakeCmd() - after, err := machine.RunCommand(cmd, args...) - if err != nil { - b.Fatalf("failed to run meminfo command: %v", err) - } - meminfo.Report(b, before, after) -} - -// TestMain is the main method for package network. -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go deleted file mode 100644 index 05a43ad17..000000000 --- a/test/benchmarks/base/startup_test.go +++ /dev/null @@ -1,144 +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 startup_test - -import ( - "context" - "fmt" - "os" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/base" - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -// BenchmarkStartEmpty times startup time for an empty container. -func BenchmarkStartupEmpty(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer machine.CleanUp() - - ctx := context.Background() - for i := 0; i < b.N; i++ { - harness.DebugLog(b, "Running container: %d", i) - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) - if _, err := container.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/alpine", - }, "true"); err != nil { - b.Fatalf("failed to run container: %v", err) - } - harness.DebugLog(b, "Ran container: %d", i) - } -} - -// BenchmarkStartupNginx times startup for a Nginx instance. -// Time is measured from start until the first request is served. -func BenchmarkStartupNginx(b *testing.B) { - // The machine to hold Nginx and the Node Server. - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer machine.CleanUp() - - ctx := context.Background() - runOpts := dockerutil.RunOpts{ - Image: "benchmarks/nginx", - } - runServerWorkload(ctx, b, - base.ServerArgs{ - Machine: machine, - RunOpts: runOpts, - Port: 80, - Cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"}, - }) -} - -// BenchmarkStartupNode times startup for a Node application instance. -// Time is measured from start until the first request is served. -// Note that the Node app connects to a Redis instance before serving. -func BenchmarkStartupNode(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer machine.CleanUp() - - ctx := context.Background() - redis, redisIP := base.RedisInstance(ctx, b, machine) - defer redis.CleanUp(ctx) - runOpts := dockerutil.RunOpts{ - Image: "benchmarks/node", - WorkDir: "/usr/src/app", - Links: []string{redis.MakeLink("redis")}, - } - - cmd := []string{"node", "index.js", redisIP.String()} - runServerWorkload(ctx, b, - base.ServerArgs{ - Machine: machine, - Port: 8080, - RunOpts: runOpts, - Cmd: cmd, - }) -} - -// runServerWorkload runs a server workload defined by 'runOpts' and 'cmd'. -// 'clientMachine' is used to connect to the server on 'serverMachine'. -func runServerWorkload(ctx context.Context, b *testing.B, args base.ServerArgs) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - harness.DebugLog(b, "Running iteration: %d", i) - if err := func() error { - server := args.Machine.GetContainer(ctx, b) - defer func() { - b.StopTimer() - // Cleanup servers as we run so that we can go indefinitely. - server.CleanUp(ctx) - b.StartTimer() - }() - harness.DebugLog(b, "Spawning container: %s", args.RunOpts.Image) - if err := server.Spawn(ctx, args.RunOpts, args.Cmd...); err != nil { - return fmt.Errorf("failed to spawn node instance: %v", err) - } - - harness.DebugLog(b, "Finding Container IP") - servingIP, err := server.FindIP(ctx, false) - if err != nil { - return fmt.Errorf("failed to get ip from server: %v", err) - } - - // 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) - } - return nil - }(); err != nil { - b.Fatal(err) - } - harness.DebugLog(b, "Ran iteration: %d", i) - } -} - -// TestMain is the main method for package network. -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/base/sysbench_test.go b/test/benchmarks/base/sysbench_test.go deleted file mode 100644 index d0f3f9261..000000000 --- a/test/benchmarks/base/sysbench_test.go +++ /dev/null @@ -1,92 +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 sysbench_test - -import ( - "context" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -type testCase struct { - name string - test tools.Sysbench -} - -// BenchmarSysbench runs sysbench on the runtime. -func BenchmarkSysbench(b *testing.B) { - testCases := []testCase{ - { - name: "CPU", - test: &tools.SysbenchCPU{ - SysbenchBase: tools.SysbenchBase{ - Threads: 1, - }, - }, - }, - { - name: "Memory", - test: &tools.SysbenchMemory{ - SysbenchBase: tools.SysbenchBase{ - Threads: 1, - }, - }, - }, - { - name: "Mutex", - test: &tools.SysbenchMutex{ - SysbenchBase: tools.SysbenchBase{ - Threads: 8, - }, - }, - }, - } - - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer machine.CleanUp() - - for _, tc := range testCases { - param := tools.Parameter{ - Name: "testname", - Value: tc.name, - } - name, err := tools.ParametersToName(param) - if err != nil { - b.Fatalf("Failed to parse params: %v", err) - } - b.Run(name, func(b *testing.B) { - ctx := context.Background() - 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", - }, 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 deleted file mode 100644 index 0b1743603..000000000 --- a/test/benchmarks/database/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -go_library( - name = "database", - testonly = 1, - srcs = ["database.go"], -) - -benchmark_test( - name = "redis_test", - size = "enormous", - srcs = ["redis_test.go"], - library = ":database", - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - ], -) diff --git a/test/benchmarks/database/database.go b/test/benchmarks/database/database.go deleted file mode 100644 index c15ca661c..000000000 --- a/test/benchmarks/database/database.go +++ /dev/null @@ -1,16 +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 database holds benchmarks around database applications. -package database diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go deleted file mode 100644 index f3c4522ac..000000000 --- a/test/benchmarks/database/redis_test.go +++ /dev/null @@ -1,129 +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 database - -import ( - "context" - "os" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// All possible operations from redis. Note: "ping" will -// run both PING_INLINE and PING_BUILD. -var operations []string = []string{ - "PING_INLINE", - "PING_BULK", - "SET", - "GET", - "INCR", - "LPUSH", - "RPUSH", - "LPOP", - "RPOP", - "SADD", - "HSET", - "SPOP", - "LRANGE_100", - "LRANGE_300", - "LRANGE_500", - "LRANGE_600", - "MSET", -} - -// BenchmarkRedis runs redis-benchmark against a redis instance and reports -// data in queries per second. Each is reported by named operation (e.g. LPUSH). -func BenchmarkRedis(b *testing.B) { - clientMachine, err := 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() - - // Redis runs on port 6379 by default. - port := 6379 - ctx := context.Background() - for _, operation := range operations { - param := tools.Parameter{ - Name: "operation", - Value: operation, - } - name, err := tools.ParametersToName(param) - if err != nil { - b.Fatalf("Failed to parse paramaters: %v", err) - } - b.Run(name, func(b *testing.B) { - server := serverMachine.GetContainer(ctx, b) - defer server.CleanUp(ctx) - - // The redis docker container takes no arguments to run a redis server. - if err := server.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - Ports: []int{port}, - }); err != nil { - b.Fatalf("failed to start redis server with: %v", err) - } - - if out, err := server.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil { - b.Fatalf("failed to start redis server: %v %s", err, out) - } - - ip, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to get IP from server: %v", err) - } - - serverPort, err := server.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to get IP from server: %v", err) - } - - if err = harness.WaitUntilServing(ctx, clientMachine, ip, serverPort); err != nil { - b.Fatalf("failed to start redis with: %v", err) - } - - client := clientMachine.GetNativeContainer(ctx, b) - defer client.CleanUp(ctx) - - redis := tools.Redis{ - Operation: operation, - } - b.ResetTimer() - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }, redis.MakeCmd(ip, serverPort, b.N /*requests*/)...) - if err != nil { - b.Fatalf("redis-benchmark failed with: %v", err) - } - b.StopTimer() - redis.Report(b, out) - }) - } -} - -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/defs.bzl b/test/benchmarks/defs.bzl deleted file mode 100644 index ef44b46e3..000000000 --- a/test/benchmarks/defs.bzl +++ /dev/null @@ -1,14 +0,0 @@ -"""Defines a rule for benchmark test targets.""" - -load("//tools:defs.bzl", "go_test") - -def benchmark_test(name, tags = [], **kwargs): - go_test( - name, - tags = [ - # Requires docker and runsc to be configured before the test runs. - "local", - "manual", - ], - **kwargs - ) diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD deleted file mode 100644 index dc82e63b2..000000000 --- a/test/benchmarks/fs/BUILD +++ /dev/null @@ -1,31 +0,0 @@ -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -benchmark_test( - name = "bazel_test", - size = "enormous", - srcs = ["bazel_test.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/cleanup", - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - "@com_github_docker_docker//api/types/mount:go_default_library", - ], -) - -benchmark_test( - name = "fio_test", - size = "enormous", - srcs = ["fio_test.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/cleanup", - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - "@com_github_docker_docker//api/types/mount:go_default_library", - ], -) diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go deleted file mode 100644 index 797b1952d..000000000 --- a/test/benchmarks/fs/bazel_test.go +++ /dev/null @@ -1,160 +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 bazel_test - -import ( - "context" - "fmt" - "os" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// Dimensions here are clean/dirty cache (do or don't drop caches) -// and if the mount on which we are compiling is a tmpfs/bind mount. -type benchmark struct { - clearCache bool // clearCache drops caches before running. - fstype harness.FileSystemType // type of filesystem to use. -} - -// Note: CleanCache versions of this test require running with root permissions. -func BenchmarkBuildABSL(b *testing.B) { - runBuildBenchmark(b, "benchmarks/absl", "/abseil-cpp", "absl/base/...") -} - -// Note: CleanCache versions of this test require running with root permissions. -// Note: This test takes on the order of 10m per permutation for runsc on kvm. -func BenchmarkBuildRunsc(b *testing.B) { - runBuildBenchmark(b, "benchmarks/runsc", "/gvisor", "runsc:runsc") -} - -func runBuildBenchmark(b *testing.B, image, workdir, target string) { - b.Helper() - // Get a machine from the Harness on which to run. - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("Failed to get machine: %v", err) - } - defer machine.CleanUp() - - benchmarks := make([]benchmark, 0, 6) - for _, filesys := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS} { - benchmarks = append(benchmarks, benchmark{ - clearCache: true, - fstype: filesys, - }) - benchmarks = append(benchmarks, benchmark{ - clearCache: false, - fstype: filesys, - }) - } - - for _, bm := range benchmarks { - pageCache := tools.Parameter{ - Name: "page_cache", - Value: "dirty", - } - if bm.clearCache { - pageCache.Value = "clean" - } - - filesystem := tools.Parameter{ - Name: "filesystem", - Value: string(bm.fstype), - } - name, err := tools.ParametersToName(pageCache, filesystem) - if err != nil { - b.Fatalf("Failed to parse parameters: %v", err) - } - - b.Run(name, func(b *testing.B) { - // Grab a container. - ctx := context.Background() - container := machine.GetContainer(ctx, b) - cu := cleanup.Make(func() { - container.CleanUp(ctx) - }) - defer cu.Clean() - mts, prefix, err := harness.MakeMount(machine, bm.fstype, &cu) - if err != nil { - b.Fatalf("Failed to make mount: %v", err) - } - - runOpts := dockerutil.RunOpts{ - Image: image, - Mounts: mts, - } - - // Start a container and sleep. - if err := container.Spawn(ctx, runOpts, "sleep", fmt.Sprintf("%d", 1000000)); err != nil { - b.Fatalf("run failed with: %v", err) - } - - cpCmd := fmt.Sprintf("mkdir -p %s && cp -r %s %s/.", prefix, workdir, prefix) - if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, - "/bin/sh", "-c", cpCmd); err != nil { - b.Fatalf("failed to copy directory: %v (%s)", err, out) - } - - 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++ { - // Drop Caches for clear cache runs. - if bm.clearCache { - if err := harness.DropCaches(machine); err != nil { - b.Skipf("failed to drop caches: %v. You probably need root.", err) - } - } - - b.StartTimer() - got, err := container.Exec(ctx, dockerutil.ExecOpts{ - WorkDir: prefix + workdir, - }, "bazel", "build", "-c", "opt", target) - if err != nil { - b.Fatalf("build failed with: %v logs: %s", err, got) - } - b.StopTimer() - - want := "Build completed successfully" - if !strings.Contains(got, want) { - b.Fatalf("string %s not in: %s", want, got) - } - - // 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) - } - } - } - }) - } -} - -// TestMain is the main method for package fs. -func TestMain(m *testing.M) { - harness.Init() - harness.SetFixedBenchmarks() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go deleted file mode 100644 index 1482466f4..000000000 --- a/test/benchmarks/fs/fio_test.go +++ /dev/null @@ -1,163 +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 fio_test - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// BenchmarkFio runs fio on the runtime under test. There are 4 basic test -// cases each run on a tmpfs mount and a bind mount. Fio requires root so that -// caches can be dropped. -func BenchmarkFio(b *testing.B) { - testCases := []tools.Fio{ - { - Test: "write", - BlockSize: 4, - IODepth: 4, - }, - { - Test: "write", - BlockSize: 1024, - IODepth: 4, - }, - { - Test: "read", - BlockSize: 4, - IODepth: 4, - }, - { - Test: "read", - BlockSize: 1024, - IODepth: 4, - }, - { - Test: "randwrite", - BlockSize: 4, - IODepth: 4, - }, - { - Test: "randread", - BlockSize: 4, - IODepth: 4, - }, - } - - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer machine.CleanUp() - - for _, fsType := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS} { - for _, tc := range testCases { - operation := tools.Parameter{ - Name: "operation", - Value: tc.Test, - } - blockSize := tools.Parameter{ - Name: "blockSize", - Value: fmt.Sprintf("%dK", tc.BlockSize), - } - filesystem := tools.Parameter{ - Name: "filesystem", - Value: string(fsType), - } - name, err := tools.ParametersToName(operation, blockSize, filesystem) - if err != nil { - b.Fatalf("Failed to parser paramters: %v", err) - } - b.Run(name, func(b *testing.B) { - b.StopTimer() - tc.Size = b.N - - ctx := context.Background() - container := machine.GetContainer(ctx, b) - cu := cleanup.Make(func() { - container.CleanUp(ctx) - }) - defer cu.Clean() - - mnts, outdir, err := harness.MakeMount(machine, fsType, &cu) - if err != nil { - b.Fatalf("failed to make mount: %v", err) - } - - // Start the container with the mount. - if err := container.Spawn( - ctx, dockerutil.RunOpts{ - Image: "benchmarks/fio", - Mounts: mnts, - }, - // Sleep on the order of b.N. - "sleep", fmt.Sprintf("%d", 1000*b.N), - ); err != nil { - b.Fatalf("failed to start fio container with: %v", err) - } - - if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, - "mkdir", "-p", outdir); err != nil { - b.Fatalf("failed to copy directory: %v (%s)", err, out) - } - - // Directory and filename inside container where fio will read/write. - outfile := filepath.Join(outdir, "test.txt") - - // 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 %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) - } - } - - // Drop caches just before running. - if err := harness.DropCaches(machine); err != nil { - b.Skipf("failed to drop caches with %v. You probably need root.", err) - } - - cmd := tc.MakeCmd(outfile) - 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() - tc.Report(b, data) - }) - } - } -} - -// TestMain is the main method for package fs. -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/harness/BUILD b/test/benchmarks/harness/BUILD deleted file mode 100644 index 367316661..000000000 --- a/test/benchmarks/harness/BUILD +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "harness", - testonly = 1, - srcs = [ - "harness.go", - "machine.go", - "util.go", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/cleanup", - "//pkg/test/dockerutil", - "//pkg/test/testutil", - "@com_github_docker_docker//api/types/mount:go_default_library", - ], -) diff --git a/test/benchmarks/harness/harness.go b/test/benchmarks/harness/harness.go deleted file mode 100644 index a853b7ba8..000000000 --- a/test/benchmarks/harness/harness.go +++ /dev/null @@ -1,57 +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 harness holds utility code for running benchmarks on Docker. -package harness - -import ( - "flag" - "fmt" - "os" - - "gvisor.dev/gvisor/pkg/test/dockerutil" -) - -var ( - help = flag.Bool("help", false, "print this usage message") - debug = flag.Bool("debug", false, "turns on debug messages for individual benchmarks") -) - -// Init performs any harness initilialization before runs. -func Init() error { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage: %s -- --test.bench=<regex>\n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - if *help { - flag.Usage() - os.Exit(0) - } - dockerutil.EnsureSupportedDockerVersion() - return nil -} - -// 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 GetMachine() (Machine, error) { - return &localMachine{}, nil -} diff --git a/test/benchmarks/harness/machine.go b/test/benchmarks/harness/machine.go deleted file mode 100644 index 405b646e8..000000000 --- a/test/benchmarks/harness/machine.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 harness - -import ( - "context" - "errors" - "net" - "os/exec" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// Machine describes a real machine for use in benchmarks. -type Machine interface { - // GetContainer gets a container from the machine. The container uses the - // runtime under test and is profiled if requested by flags. - GetContainer(ctx context.Context, log testutil.Logger) *dockerutil.Container - - // GetNativeContainer gets a native container from the machine. Native containers - // use runc by default and are not profiled. - GetNativeContainer(ctx context.Context, log testutil.Logger) *dockerutil.Container - - // RunCommand runs cmd on this machine. - RunCommand(cmd string, args ...string) (string, error) - - // Returns IP Address for the machine. - IPAddress() (net.IP, error) - - // CleanUp cleans up this machine. - CleanUp() -} - -// localMachine describes this machine. -type localMachine struct { -} - -// GetContainer implements Machine.GetContainer for localMachine. -func (l *localMachine) GetContainer(ctx context.Context, logger testutil.Logger) *dockerutil.Container { - return dockerutil.MakeContainer(ctx, logger) -} - -// GetContainer implements Machine.GetContainer for localMachine. -func (l *localMachine) GetNativeContainer(ctx context.Context, logger testutil.Logger) *dockerutil.Container { - return dockerutil.MakeNativeContainer(ctx, logger) -} - -// RunCommand implements Machine.RunCommand for localMachine. -func (l *localMachine) RunCommand(cmd string, args ...string) (string, error) { - c := exec.Command(cmd, args...) - out, err := c.CombinedOutput() - return string(out), err -} - -// IPAddress implements Machine.IPAddress. -func (l *localMachine) IPAddress() (net.IP, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return net.IP{}, err - } - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP, nil - } - } - } - // Unable to locate non-loopback address. - return nil, errors.New("no IPAddress available") -} - -// CleanUp implements Machine.CleanUp and does nothing for localMachine. -func (*localMachine) CleanUp() { -} diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go deleted file mode 100644 index f7e569751..000000000 --- a/test/benchmarks/harness/util.go +++ /dev/null @@ -1,113 +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 harness - -import ( - "context" - "fmt" - "net" - "strings" - "testing" - - "github.com/docker/docker/api/types/mount" - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -//TODO(gvisor.dev/issue/3535): move to own package or move methods to harness struct. - -// WaitUntilServing grabs a container from `machine` and waits for a server at -// IP:port. -func WaitUntilServing(ctx context.Context, machine Machine, server net.IP, port int) error { - var logger testutil.DefaultLogger = "util" - netcat := machine.GetNativeContainer(ctx, logger) - defer netcat.CleanUp(ctx) - - cmd := fmt.Sprintf("while ! wget -q --spider http://%s:%d; do true; done", server, port) - _, err := netcat.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/util", - }, "sh", "-c", cmd) - return err -} - -// DropCaches drops caches on the provided machine. Requires root. -func DropCaches(machine Machine) error { - if out, err := machine.RunCommand("/bin/sh", "-c", "sync && sysctl vm.drop_caches=3"); err != nil { - return fmt.Errorf("failed to drop caches: %v logs: %s", err, out) - } - return nil -} - -// DebugLog prints debug messages if the debug flag is set. -func DebugLog(b *testing.B, msg string, args ...interface{}) { - b.Helper() - if *debug { - b.Logf(msg, args...) - } -} - -// FileSystemType represents a type container mount. -type FileSystemType string - -const ( - // BindFS indicates a bind mount should be created. - BindFS FileSystemType = "bindfs" - // TmpFS indicates a tmpfs mount should be created. - TmpFS FileSystemType = "tmpfs" - // RootFS indicates no mount should be created and the root mount should be used. - RootFS FileSystemType = "rootfs" -) - -// MakeMount makes a mount and cleanup based on the requested type. Bind -// and volume mounts are backed by a temp directory made with mktemp. -// tmpfs mounts require no such backing and are just made. -// rootfs mounts do not make a mount, but instead return a target direectory at root. -// It is up to the caller to call Clean on the passed *cleanup.Cleanup -func MakeMount(machine Machine, fsType FileSystemType, cu *cleanup.Cleanup) ([]mount.Mount, string, error) { - mounts := make([]mount.Mount, 0, 1) - target := "/data" - switch fsType { - case BindFS: - dir, err := machine.RunCommand("mktemp", "-d") - if err != nil { - return mounts, "", fmt.Errorf("failed to create tempdir: %v", err) - } - dir = strings.TrimSuffix(dir, "\n") - cu.Add(func() { - machine.RunCommand("rm", "-rf", dir) - }) - out, err := machine.RunCommand("chmod", "777", dir) - if err != nil { - return mounts, "", fmt.Errorf("failed modify directory: %v %s", err, out) - } - mounts = append(mounts, mount.Mount{ - Target: target, - Source: dir, - Type: mount.TypeBind, - }) - return mounts, target, nil - case RootFS: - return mounts, target, nil - case TmpFS: - mounts = append(mounts, mount.Mount{ - Target: target, - Type: mount.TypeTmpfs, - }) - return mounts, target, nil - default: - return mounts, "", fmt.Errorf("illegal mount type not supported: %v", fsType) - } -} diff --git a/test/benchmarks/media/BUILD b/test/benchmarks/media/BUILD deleted file mode 100644 index 380783f0b..000000000 --- a/test/benchmarks/media/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -go_library( - name = "media", - testonly = 1, - srcs = ["media.go"], -) - -benchmark_test( - name = "ffmpeg_test", - size = "enormous", - srcs = ["ffmpeg_test.go"], - library = ":media", - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - ], -) diff --git a/test/benchmarks/media/ffmpeg_test.go b/test/benchmarks/media/ffmpeg_test.go deleted file mode 100644 index 1b99a319a..000000000 --- a/test/benchmarks/media/ffmpeg_test.go +++ /dev/null @@ -1,61 +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 media - -import ( - "context" - "os" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -// BenchmarkFfmpeg runs ffmpeg in a container and records runtime. -// BenchmarkFfmpeg should run as root to drop caches. -func BenchmarkFfmpeg(b *testing.B) { - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer machine.CleanUp() - - ctx := context.Background() - 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++ { - 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() - 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 deleted file mode 100644 index ed7b24651..000000000 --- a/test/benchmarks/media/media.go +++ /dev/null @@ -1,16 +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 media holds benchmarks around media processing applications. -package media diff --git a/test/benchmarks/ml/BUILD b/test/benchmarks/ml/BUILD deleted file mode 100644 index 3425b8dad..000000000 --- a/test/benchmarks/ml/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -go_library( - name = "ml", - testonly = 1, - srcs = ["ml.go"], -) - -benchmark_test( - name = "tensorflow_test", - size = "enormous", - srcs = ["tensorflow_test.go"], - library = ":ml", - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - ], -) diff --git a/test/benchmarks/ml/ml.go b/test/benchmarks/ml/ml.go deleted file mode 100644 index d5fc5b7da..000000000 --- a/test/benchmarks/ml/ml.go +++ /dev/null @@ -1,16 +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 ml holds benchmarks around machine learning performance. -package ml diff --git a/test/benchmarks/ml/tensorflow_test.go b/test/benchmarks/ml/tensorflow_test.go deleted file mode 100644 index 8fa75d778..000000000 --- a/test/benchmarks/ml/tensorflow_test.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 ml - -import ( - "context" - "os" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// BenchmarkTensorflow runs workloads from a TensorFlow tutorial. -// See: https://github.com/aymericdamien/TensorFlow-Examples -func BenchmarkTensorflow(b *testing.B) { - workloads := map[string]string{ - "GradientDecisionTree": "2_BasicModels/gradient_boosted_decision_tree.py", - "Kmeans": "2_BasicModels/kmeans.py", - "LogisticRegression": "2_BasicModels/logistic_regression.py", - "NearestNeighbor": "2_BasicModels/nearest_neighbor.py", - "RandomForest": "2_BasicModels/random_forest.py", - "ConvolutionalNetwork": "3_NeuralNetworks/convolutional_network.py", - "MultilayerPerceptron": "3_NeuralNetworks/multilayer_perceptron.py", - "NeuralNetwork": "3_NeuralNetworks/neural_network.py", - } - - machine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer machine.CleanUp() - - for name, workload := range workloads { - runName, err := tools.ParametersToName(tools.Parameter{ - Name: "operation", - Value: name, - }) - if err != nil { - b.Fatalf("Faile to parse param: %v", err) - } - - b.Run(runName, func(b *testing.B) { - ctx := context.Background() - - b.ResetTimer() - b.StopTimer() - - for i := 0; i < b.N; i++ { - 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) - } - - // Run tensorflow. - b.StartTimer() - if out, err := container.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/tensorflow", - Env: []string{"PYTHONPATH=$PYTHONPATH:/TensorFlow-Examples/examples"}, - WorkDir: "/TensorFlow-Examples/examples", - }, "python", workload); err != nil { - b.Fatalf("failed to run container: %v logs: %s", err, out) - } - 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 deleted file mode 100644 index 2741570f5..000000000 --- a/test/benchmarks/network/BUILD +++ /dev/null @@ -1,94 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//test/benchmarks:defs.bzl", "benchmark_test") - -package(licenses = ["notice"]) - -go_library( - name = "network", - testonly = 1, - srcs = [ - "network.go", - ], - deps = [ - "//pkg/test/dockerutil", - "//test/benchmarks/harness", - "//test/benchmarks/tools", - ], -) - -benchmark_test( - name = "iperf_test", - size = "enormous", - srcs = [ - "iperf_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 deleted file mode 100644 index 629127250..000000000 --- a/test/benchmarks/network/httpd_test.go +++ /dev/null @@ -1,135 +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 ( - "os" - "strconv" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// see Dockerfile '//images/benchmarks/httpd'. -var httpdDocs = map[string]string{ - "notfound": "notfound", - "1Kb": "latin1k.txt", - "10Kb": "latin10k.txt", - "100Kb": "latin100k.txt", - "1Mb": "latin1024k.txt", - "10Mb": "latin10240k.txt", -} - -// 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) -} - -// 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) { - b.Helper() - for size, filename := range httpdDocs { - concurrency := []int{1, 25, 50, 100, 1000} - for _, c := range concurrency { - fsize := tools.Parameter{ - Name: "filesize", - Value: size, - } - concurrency := tools.Parameter{ - Name: "concurrency", - Value: strconv.Itoa(c), - } - name, err := tools.ParametersToName(fsize, concurrency) - 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) - }) - } - } -} - -// 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) { - // httpd runs on port 80. - port := 80 - httpdRunOpts := dockerutil.RunOpts{ - Image: "benchmarks/httpd", - Ports: []int{port}, - Env: []string{ - // Standard environmental variables for httpd. - "APACHE_RUN_DIR=/tmp", - "APACHE_RUN_USER=nobody", - "APACHE_RUN_GROUP=nogroup", - "APACHE_LOG_DIR=/tmp", - "APACHE_PID_FILE=/tmp/apache.pid", - }, - } - httpdCmd := []string{"sh", "-c", "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"} - 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 deleted file mode 100644 index 6ac7717b1..000000000 --- a/test/benchmarks/network/iperf_test.go +++ /dev/null @@ -1,121 +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" - "os" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -func BenchmarkIperf(b *testing.B) { - 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() - ctx := context.Background() - for _, bm := range []struct { - name string - clientFunc func(context.Context, testutil.Logger) *dockerutil.Container - serverFunc func(context.Context, testutil.Logger) *dockerutil.Container - }{ - // We are either measuring the server or the client. The other should be - // runc. e.g. Upload sees how fast the runtime under test uploads to a native - // server. - { - name: "Upload", - clientFunc: clientMachine.GetContainer, - serverFunc: serverMachine.GetNativeContainer, - }, - { - name: "Download", - clientFunc: clientMachine.GetNativeContainer, - serverFunc: serverMachine.GetContainer, - }, - } { - name, err := tools.ParametersToName(tools.Parameter{ - Name: "operation", - Value: bm.name, - }) - if err != nil { - b.Fatalf("Failed to parse parameters: %v", err) - } - b.Run(name, func(b *testing.B) { - // Set up the containers. - server := bm.serverFunc(ctx, b) - defer server.CleanUp(ctx) - client := bm.clientFunc(ctx, b) - defer client.CleanUp(ctx) - - // iperf serves on port 5001 by default. - port := 5001 - - // Start the server. - if err := server.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/iperf", - Ports: []int{port}, - }, "iperf", "-s"); err != nil { - b.Fatalf("failed to start server with: %v", err) - } - - ip, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to find server ip: %v", err) - } - - servingPort, err := server.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to find port %d: %v", port, err) - } - - // Make sure the server is up and serving before we run. - if err := harness.WaitUntilServing(ctx, clientMachine, ip, servingPort); err != nil { - b.Fatalf("failed to wait for server: %v", err) - } - - iperf := tools.Iperf{ - Num: b.N, // KB for the client to send. - } - - // Run the client. - b.ResetTimer() - 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() - }) - } -} - -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 deleted file mode 100644 index d61002cea..000000000 --- a/test/benchmarks/network/network.go +++ /dev/null @@ -1,80 +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 holds benchmarks around raw network performance. -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) { - ctx := context.Background() - - // 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 deleted file mode 100644 index 74f3578fc..000000000 --- a/test/benchmarks/network/nginx_test.go +++ /dev/null @@ -1,147 +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 ( - "os" - "strconv" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// see Dockerfile '//images/benchmarks/nginx'. -var nginxDocs = map[string]string{ - "notfound": "notfound", - "1Kb": "latin1k.txt", - "10Kb": "latin10k.txt", - "100Kb": "latin100k.txt", - "1Mb": "latin1024k.txt", - "10Mb": "latin10240k.txt", -} - -// BenchmarkNginxDocSize iterates over different sized payloads, testing how -// well the runtime handles sending different payload sizes. -func BenchmarkNginxDocSize(b *testing.B) { - benchmarkNginxDocSize(b, true /* tmpfs */) - benchmarkNginxDocSize(b, false /* 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, tmpfs bool) { - for size, filename := range nginxDocs { - concurrency := []int{1, 25, 50, 100, 1000} - 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: "bind", - } - if tmpfs { - fs.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: 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: b.N, - Concurrency: c, - Doc: filename, - } - runNginx(b, hey, true /*tmpfs*/) - }) - } - } -} - -// runNginx configures the static serving methods to run httpd. -func runNginx(b *testing.B, hey *tools.Hey, tmpfs bool) { - // nginx runs on port 80. - port := 80 - nginxRunOpts := dockerutil.RunOpts{ - Image: "benchmarks/nginx", - Ports: []int{port}, - } - - nginxCmd := []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"} - if tmpfs { - nginxCmd = []string{"sh", "-c", "mkdir -p /tmp/html && cp -a /local/* /tmp/html && nginx -c /etc/nginx/nginx.conf"} - } - - // Command copies nginxDocs to tmpfs serving directory and runs nginx. - 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 deleted file mode 100644 index a1fc82f95..000000000 --- a/test/benchmarks/network/node_test.go +++ /dev/null @@ -1,137 +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" - "os" - "strconv" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// BenchmarkNode runs requests using 'hey' against a Node server run on -// 'runtime'. The server responds to requests by grabbing some data in a -// redis instance and returns the data in its reponse. The test loops through -// increasing amounts of concurency for requests. -func BenchmarkNode(b *testing.B) { - concurrency := []int{1, 5, 10, 25} - for _, c := range concurrency { - param := tools.Parameter{ - Name: "concurrency", - Value: strconv.Itoa(c), - } - name, err := tools.ParametersToName(param) - 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, - } - runNode(b, hey) - }) - } -} - -// runNode runs the test for a given # of requests and concurrency. -func runNode(b *testing.B, hey *tools.Hey) { - b.Helper() - - // The machine to hold Redis and the Node Server. - serverMachine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer serverMachine.CleanUp() - - // The machine to run 'hey'. - clientMachine, err := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer clientMachine.CleanUp() - - ctx := context.Background() - - // Spawn a redis instance for the app to use. - redis := serverMachine.GetNativeContainer(ctx, b) - if err := redis.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }); err != nil { - b.Fatalf("failed to spwan redis instance: %v", err) - } - defer redis.CleanUp(ctx) - - if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil { - b.Fatalf("failed to start redis server: %v %s", err, out) - } - redisIP, err := redis.FindIP(ctx, false) - if err != nil { - b.Fatalf("failed to get IP from redis instance: %v", err) - } - - // Node runs on port 8080. - port := 8080 - - // Start-up the Node server. - nodeApp := serverMachine.GetContainer(ctx, b) - if err := nodeApp.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/node", - WorkDir: "/usr/src/app", - Links: []string{redis.MakeLink("redis")}, - Ports: []int{port}, - }, "node", "index.js", redisIP.String()); err != nil { - b.Fatalf("failed to spawn node instance: %v", err) - } - defer nodeApp.CleanUp(ctx) - - servingIP, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to get ip from server: %v", err) - } - - servingPort, err := nodeApp.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to port from node instance: %v", err) - } - - // Wait until the Client sees the server as up. - harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort) - - heyCmd := hey.MakeCmd(servingIP, servingPort) - - // the client should run on Native. - b.ResetTimer() - client := clientMachine.GetNativeContainer(ctx, b) - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/hey", - }, heyCmd...) - if err != nil { - b.Fatalf("hey container failed: %v logs: %s", err, out) - } - - // Stop the timer to parse the data and report stats. - hey.Report(b, out) -} - -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 deleted file mode 100644 index b7ec16e0a..000000000 --- a/test/benchmarks/network/ruby_test.go +++ /dev/null @@ -1,144 +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" - "fmt" - "os" - "strconv" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// BenchmarkRuby runs requests using 'hey' against a ruby application server. -// On start, ruby app generates some random data and pushes it to a redis -// instance. On a request, the app grabs for random entries from the redis -// server, publishes it to a document, and returns the doc to the request. -func BenchmarkRuby(b *testing.B) { - concurrency := []int{1, 5, 10, 25} - for _, c := range concurrency { - param := tools.Parameter{ - Name: "concurrency", - Value: strconv.Itoa(c), - } - name, err := tools.ParametersToName(param) - 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, - } - runRuby(b, hey) - }) - } -} - -// runRuby runs the test for a given # of requests and concurrency. -func runRuby(b *testing.B, hey *tools.Hey) { - // The machine to hold Redis and the Ruby Server. - 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 := harness.GetMachine() - if err != nil { - b.Fatalf("failed to get machine with: %v", err) - } - defer clientMachine.CleanUp() - ctx := context.Background() - - // Spawn a redis instance for the app to use. - redis := serverMachine.GetNativeContainer(ctx, b) - if err := redis.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }); err != nil { - b.Fatalf("failed to spwan redis instance: %v", err) - } - defer redis.CleanUp(ctx) - - if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil { - b.Fatalf("failed to start redis server: %v %s", err, out) - } - redisIP, err := redis.FindIP(ctx, false) - if err != nil { - b.Fatalf("failed to get IP from redis instance: %v", err) - } - - // Ruby runs on port 9292. - const port = 9292 - - // Start-up the Ruby server. - rubyApp := serverMachine.GetContainer(ctx, b) - if err := rubyApp.Spawn(ctx, dockerutil.RunOpts{ - Image: "benchmarks/ruby", - WorkDir: "/app", - Links: []string{redis.MakeLink("redis")}, - Ports: []int{port}, - Env: []string{ - fmt.Sprintf("PORT=%d", port), - "WEB_CONCURRENCY=20", - "WEB_MAX_THREADS=20", - "RACK_ENV=production", - fmt.Sprintf("HOST=%s", redisIP), - }, - User: "nobody", - }, "sh", "-c", "/usr/bin/puma"); err != nil { - b.Fatalf("failed to spawn node instance: %v", err) - } - defer rubyApp.CleanUp(ctx) - - servingIP, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to get ip from server: %v", err) - } - - servingPort, err := rubyApp.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to port from node instance: %v", err) - } - - // Wait until the Client sees the server as up. - if err := harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort); err != nil { - b.Fatalf("failed to wait until serving: %v", err) - } - heyCmd := hey.MakeCmd(servingIP, servingPort) - - // the client should run on Native. - b.ResetTimer() - client := clientMachine.GetNativeContainer(ctx, b) - defer client.CleanUp(ctx) - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/hey", - }, heyCmd...) - if err != nil { - b.Fatalf("hey container failed: %v logs: %s", err, out) - } - b.StopTimer() - hey.Report(b, out) -} - -func TestMain(m *testing.M) { - harness.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/tcp/BUILD b/test/benchmarks/tcp/BUILD deleted file mode 100644 index 6dde7d9e6..000000000 --- a/test/benchmarks/tcp/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "tcp_proxy", - srcs = ["tcp_proxy.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/adapters/gonet", - "//pkg/tcpip/link/fdbased", - "//pkg/tcpip/link/qdisc/fifo", - "//pkg/tcpip/network/arp", - "//pkg/tcpip/network/ipv4", - "//pkg/tcpip/stack", - "//pkg/tcpip/transport/tcp", - "//pkg/tcpip/transport/udp", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -# nsjoin is a trivial replacement for nsenter. This is used because nsenter is -# not available on all systems where this benchmark is run (and we aim to -# minimize external dependencies.) - -cc_binary( - name = "nsjoin", - srcs = ["nsjoin.c"], - visibility = ["//:sandbox"], -) - -sh_binary( - name = "tcp_benchmark", - srcs = ["tcp_benchmark.sh"], - data = [ - ":nsjoin", - ":tcp_proxy", - ], - visibility = ["//:sandbox"], -) diff --git a/test/benchmarks/tcp/README.md b/test/benchmarks/tcp/README.md deleted file mode 100644 index 38e6e69f0..000000000 --- a/test/benchmarks/tcp/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# TCP Benchmarks - -This directory contains a standardized TCP benchmark. This helps to evaluate the -performance of netstack and native networking stacks under various conditions. - -## `tcp_benchmark` - -This benchmark allows TCP throughput testing under various conditions. The setup -consists of an iperf client, a client proxy, a server proxy and an iperf server. -The client proxy and server proxy abstract the network mechanism used to -communicate between the iperf client and server. - -The setup looks like the following: - -``` - +--------------+ (native) +--------------+ - | iperf client |[lo @ 10.0.0.1]------>| client proxy | - +--------------+ +--------------+ - [client.0 @ 10.0.0.2] - (netstack) | | (native) - +------+-----+ - | - [br0] - | - Network emulation applied ---> [wan.0:wan.1] - | - [br1] - | - +------+-----+ - (netstack) | | (native) - [server.0 @ 10.0.0.3] - +--------------+ +--------------+ - | iperf server |<------[lo @ 10.0.0.4]| server proxy | - +--------------+ (native) +--------------+ -``` - -Different configurations can be run using different arguments. For example: - -* Native test under normal internet conditions: `tcp_benchmark` -* Native test under ideal conditions: `tcp_benchmark --ideal` -* Netstack client under ideal conditions: `tcp_benchmark --client --ideal` -* Netstack client with 5% packet loss: `tcp_benchmark --client --ideal --loss - 5` - -Use `tcp_benchmark --help` for full arguments. - -This tool may be used to easily generate data for graphing. For example, to -generate a CSV for various latencies, you might do: - -``` -rm -f /tmp/netstack_latency.csv /tmp/native_latency.csv -latencies=$(seq 0 5 50; - seq 60 10 100; - seq 125 25 250; - seq 300 50 500) -for latency in $latencies; do - read throughput client_cpu server_cpu <<< \ - $(./tcp_benchmark --duration 30 --client --ideal --latency $latency) - echo $latency,$throughput,$client_cpu >> /tmp/netstack_latency.csv -done -for latency in $latencies; do - read throughput client_cpu server_cpu <<< \ - $(./tcp_benchmark --duration 30 --ideal --latency $latency) - echo $latency,$throughput,$client_cpu >> /tmp/native_latency.csv -done -``` - -Similarly, to generate a CSV for various levels of packet loss, the following -would be appropriate: - -``` -rm -f /tmp/netstack_loss.csv /tmp/native_loss.csv -losses=$(seq 0 0.1 1.0; - seq 1.2 0.2 2.0; - seq 2.5 0.5 5.0; - seq 6.0 1.0 10.0) -for loss in $losses; do - read throughput client_cpu server_cpu <<< \ - $(./tcp_benchmark --duration 30 --client --ideal --latency 10 --loss $loss) - echo $loss,$throughput,$client_cpu >> /tmp/netstack_loss.csv -done -for loss in $losses; do - read throughput client_cpu server_cpu <<< \ - $(./tcp_benchmark --duration 30 --ideal --latency 10 --loss $loss) - echo $loss,$throughput,$client_cpu >> /tmp/native_loss.csv -done -``` diff --git a/test/benchmarks/tcp/nsjoin.c b/test/benchmarks/tcp/nsjoin.c deleted file mode 100644 index 524b4d549..000000000 --- a/test/benchmarks/tcp/nsjoin.c +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include <errno.h> -#include <fcntl.h> -#include <sched.h> -#include <stdio.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -int main(int argc, char** argv) { - if (argc <= 2) { - fprintf(stderr, "error: must provide a namespace file.\n"); - fprintf(stderr, "usage: %s <file> [arguments...]\n", argv[0]); - return 1; - } - - int fd = open(argv[1], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "error opening %s: %s\n", argv[1], strerror(errno)); - return 1; - } - if (setns(fd, 0) < 0) { - fprintf(stderr, "error joining %s: %s\n", argv[1], strerror(errno)); - return 1; - } - - execvp(argv[2], &argv[2]); - return 1; -} diff --git a/test/benchmarks/tcp/tcp_benchmark.sh b/test/benchmarks/tcp/tcp_benchmark.sh deleted file mode 100755 index 6a4f33b96..000000000 --- a/test/benchmarks/tcp/tcp_benchmark.sh +++ /dev/null @@ -1,396 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# TCP benchmark; see README.md for documentation. - -# Fixed parameters. -iperf_port=45201 # Not likely to be privileged. -proxy_port=44000 # Ditto. -client_addr=10.0.0.1 -client_proxy_addr=10.0.0.2 -server_proxy_addr=10.0.0.3 -server_addr=10.0.0.4 -mask=8 - -# Defaults; this provides a reasonable approximation of a decent internet link. -# Parameters can be varied independently from this set to see response to -# various changes in the kind of link available. -client=false -server=false -verbose=false -gso=0 -swgso=false -mtu=1280 # 1280 is a reasonable lowest-common-denominator. -latency=10 # 10ms approximates a fast, dedicated connection. -latency_variation=1 # +/- 1ms is a relatively low amount of jitter. -loss=0.1 # 0.1% loss is non-zero, but not extremely high. -duplicate=0.1 # 0.1% means duplicates are 1/10x as frequent as losses. -duration=30 # 30s is enough time to consistent results (experimentally). -helper_dir=$(dirname $0) -netstack_opts= -disable_linux_gso= -num_client_threads=1 - -# Check for netem support. -lsmod_output=$(lsmod | grep sch_netem) -if [ "$?" != "0" ]; then - echo "warning: sch_netem may not be installed." >&2 -fi - -while [ $# -gt 0 ]; do - case "$1" in - --client) - client=true - ;; - --client_tcp_probe_file) - shift - netstack_opts="${netstack_opts} -client_tcp_probe_file=$1" - ;; - --server) - server=true - ;; - --verbose) - verbose=true - ;; - --gso) - shift - gso=$1 - ;; - --swgso) - swgso=true - ;; - --server_tcp_probe_file) - shift - netstack_opts="${netstack_opts} -server_tcp_probe_file=$1" - ;; - --ideal) - mtu=1500 # Standard ethernet. - latency=0 # No latency. - latency_variation=0 # No jitter. - loss=0 # No loss. - duplicate=0 # No duplicates. - ;; - --mtu) - shift - [ "$#" -le 0 ] && echo "no mtu provided" && exit 1 - mtu=$1 - ;; - --sack) - netstack_opts="${netstack_opts} -sack" - ;; - --rack) - netstack_opts="${netstack_opts} -rack" - ;; - --cubic) - netstack_opts="${netstack_opts} -cubic" - ;; - --moderate-recv-buf) - netstack_opts="${netstack_opts} -moderate_recv_buf" - ;; - --duration) - shift - [ "$#" -le 0 ] && echo "no duration provided" && exit 1 - duration=$1 - ;; - --latency) - shift - [ "$#" -le 0 ] && echo "no latency provided" && exit 1 - latency=$1 - ;; - --latency-variation) - shift - [ "$#" -le 0 ] && echo "no latency variation provided" && exit 1 - latency_variation=$1 - ;; - --loss) - shift - [ "$#" -le 0 ] && echo "no loss probability provided" && exit 1 - loss=$1 - ;; - --duplicate) - shift - [ "$#" -le 0 ] && echo "no duplicate provided" && exit 1 - duplicate=$1 - ;; - --cpuprofile) - shift - netstack_opts="${netstack_opts} -cpuprofile=$1" - ;; - --memprofile) - shift - netstack_opts="${netstack_opts} -memprofile=$1" - ;; - --disable-linux-gso) - disable_linux_gso=1 - ;; - --num-client-threads) - shift - num_client_threads=$1 - ;; - --helpers) - shift - [ "$#" -le 0 ] && echo "no helper dir provided" && exit 1 - helper_dir=$1 - ;; - *) - echo "usage: $0 [options]" - echo "options:" - echo " --help show this message" - echo " --verbose verbose output" - echo " --client use netstack as the client" - echo " --ideal reset all network emulation" - echo " --server use netstack as the server" - echo " --mtu set the mtu (bytes)" - echo " --sack enable SACK support" - echo " --rack enable RACK support" - echo " --moderate-recv-buf enable TCP receive buffer auto-tuning" - echo " --cubic enable CUBIC congestion control for Netstack" - echo " --duration set the test duration (s)" - echo " --latency set the latency (ms)" - echo " --latency-variation set the latency variation" - echo " --loss set the loss probability (%)" - echo " --duplicate set the duplicate probability (%)" - echo " --helpers set the helper directory" - echo " --num-client-threads number of parallel client threads to run" - echo " --disable-linux-gso disable segmentation offload in the Linux network stack" - echo "" - echo "The output will of the script will be:" - echo " <throughput> <client-cpu-usage> <server-cpu-usage>" - exit 1 - esac - shift -done - -if [ ${verbose} == "true" ]; then - set -x -fi - -# Latency needs to be halved, since it's applied on both ways. -half_latency=$(echo ${latency}/2 | bc -l | awk '{printf "%1.2f", $0}') -half_loss=$(echo ${loss}/2 | bc -l | awk '{printf "%1.6f", $0}') -half_duplicate=$(echo ${duplicate}/2 | bc -l | awk '{printf "%1.6f", $0}') -helper_dir=${helper_dir#$(pwd)/} # Use relative paths. -proxy_binary=${helper_dir}/tcp_proxy -nsjoin_binary=${helper_dir}/nsjoin - -if [ ! -e ${proxy_binary} ]; then - echo "Could not locate ${proxy_binary}, please make sure you've built the binary" - exit 1 -fi - -if [ ! -e ${nsjoin_binary} ]; then - echo "Could not locate ${nsjoin_binary}, please make sure you've built the binary" - exit 1 -fi - -if [ $(echo ${latency_variation} | awk '{printf "%1.2f", $0}') != "0.00" ]; then - # As long as there's some jitter, then we use the paretonormal distribution. - # This will preserve the minimum RTT, but add a realistic amount of jitter to - # the connection and cause re-ordering, etc. The regular pareto distribution - # appears to an unreasonable level of delay (we want only small spikes.) - distribution="distribution paretonormal" -else - distribution="" -fi - -# Client proxy that will listen on the client's iperf target forward traffic -# using the host networking stack. -client_args="${proxy_binary} -port ${proxy_port} -forward ${server_proxy_addr}:${proxy_port}" -if ${client}; then - # Client proxy that will listen on the client's iperf target - # and forward traffic using netstack. - client_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -client \\ - -mtu ${mtu} -iface client.0 -addr ${client_proxy_addr} -mask ${mask} \\ - -forward ${server_proxy_addr}:${proxy_port} -gso=${gso} -swgso=${swgso}" -fi - -# Server proxy that will listen on the proxy port and forward to the server's -# iperf server using the host networking stack. -server_args="${proxy_binary} -port ${proxy_port} -forward ${server_addr}:${iperf_port}" -if ${server}; then - # Server proxy that will listen on the proxy port and forward to the servers' - # iperf server using netstack. - server_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -server \\ - -mtu ${mtu} -iface server.0 -addr ${server_proxy_addr} -mask ${mask} \\ - -forward ${server_addr}:${iperf_port} -gso=${gso} -swgso=${swgso}" -fi - -# Specify loss and duplicate parameters only if they are non-zero -loss_opt="" -if [ "$(echo $half_loss | bc -q)" != "0" ]; then - loss_opt="loss random ${half_loss}%" -fi -duplicate_opt="" -if [ "$(echo $half_duplicate | bc -q)" != "0" ]; then - duplicate_opt="duplicate ${half_duplicate}%" -fi - -exec unshare -U -m -n -r -f -p --mount-proc /bin/bash << EOF -set -e -m - -if [ ${verbose} == "true" ]; then - set -x -fi - -mount -t tmpfs netstack-bench /tmp - -# We may have reset the path in the unshare if the shell loaded some public -# profiles. Ensure that tools are discoverable via the parent's PATH. -export PATH=${PATH} - -# Add client, server interfaces. -ip link add client.0 type veth peer name client.1 -ip link add server.0 type veth peer name server.1 - -# Add network emulation devices. -ip link add wan.0 type veth peer name wan.1 -ip link set wan.0 up -ip link set wan.1 up - -# Enroll on the bridge. -ip link add name br0 type bridge -ip link add name br1 type bridge -ip link set client.1 master br0 -ip link set server.1 master br1 -ip link set wan.0 master br0 -ip link set wan.1 master br1 -ip link set br0 up -ip link set br1 up - -# Set the MTU appropriately. -ip link set client.0 mtu ${mtu} -ip link set server.0 mtu ${mtu} -ip link set wan.0 mtu ${mtu} -ip link set wan.1 mtu ${mtu} - -# Add appropriate latency, loss and duplication. -# -# This is added in at the point of bridge connection. -for device in wan.0 wan.1; do - # NOTE: We don't support a loss correlation as testing has shown that it - # actually doesn't work. The man page actually has a small comment about this - # "It is also possible to add a correlation, but this option is now deprecated - # due to the noticed bad behavior." For more information see netem(8). - tc qdisc add dev \$device root netem \\ - delay ${half_latency}ms ${latency_variation}ms ${distribution} \\ - ${loss_opt} ${duplicate_opt} -done - -# Start a client proxy. -touch /tmp/client.netns -unshare -n mount --bind /proc/self/ns/net /tmp/client.netns - -# Move the endpoint into the namespace. -while ip link | grep client.0 > /dev/null; do - ip link set dev client.0 netns /tmp/client.netns -done - -if ! ${client}; then - # Only add the address to NIC if netstack is not in use. Otherwise the host - # will also process the inbound SYN and send a RST back. - ${nsjoin_binary} /tmp/client.netns ip addr add ${client_proxy_addr}/${mask} dev client.0 -fi - -# Start a server proxy. -touch /tmp/server.netns -unshare -n mount --bind /proc/self/ns/net /tmp/server.netns -# Move the endpoint into the namespace. -while ip link | grep server.0 > /dev/null; do - ip link set dev server.0 netns /tmp/server.netns -done -if ! ${server}; then - # Only add the address to NIC if netstack is not in use. Otherwise the host - # will also process the inbound SYN and send a RST back. - ${nsjoin_binary} /tmp/server.netns ip addr add ${server_proxy_addr}/${mask} dev server.0 -fi - -# Add client and server addresses, and bring everything up. -${nsjoin_binary} /tmp/client.netns ip addr add ${client_addr}/${mask} dev client.0 -${nsjoin_binary} /tmp/server.netns ip addr add ${server_addr}/${mask} dev server.0 -if [ "${disable_linux_gso}" == "1" ]; then - ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 tso off - ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gro off - ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gso off - ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 tso off - ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gso off - ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gro off -fi -${nsjoin_binary} /tmp/client.netns ip link set client.0 up -${nsjoin_binary} /tmp/client.netns ip link set lo up -${nsjoin_binary} /tmp/server.netns ip link set server.0 up -${nsjoin_binary} /tmp/server.netns ip link set lo up -ip link set dev client.1 up -ip link set dev server.1 up - -${nsjoin_binary} /tmp/client.netns ${client_args} & -client_pid=\$! -${nsjoin_binary} /tmp/server.netns ${server_args} & -server_pid=\$! - -# Start the iperf server. -${nsjoin_binary} /tmp/server.netns iperf -p ${iperf_port} -s >&2 & -iperf_pid=\$! - -# Show traffic information. -if ! ${client} && ! ${server}; then - ${nsjoin_binary} /tmp/client.netns ping -c 100 -i 0.001 -W 1 ${server_addr} >&2 || true -fi - -results_file=\$(mktemp) -function cleanup { - rm -f \$results_file - kill -TERM \$client_pid - kill -TERM \$server_pid - wait \$client_pid - wait \$server_pid - kill -9 \$iperf_pid 2>/dev/null -} - -# Allow failure from this point. -set +e -trap cleanup EXIT - -# Run the benchmark, recording the results file. -while ${nsjoin_binary} /tmp/client.netns iperf \\ - -p ${proxy_port} -c ${client_addr} -t ${duration} -f m -P ${num_client_threads} 2>&1 \\ - | tee \$results_file \\ - | grep "connect failed" >/dev/null; do - sleep 0.1 # Wait for all services. -done - -# Unlink all relevant devices from the bridge. This is because when the bridge -# is deleted, the kernel may hang. It appears that this problem is fixed in -# upstream commit 1ce5cce895309862d2c35d922816adebe094fe4a. -ip link set client.1 nomaster -ip link set server.1 nomaster -ip link set wan.0 nomaster -ip link set wan.1 nomaster - -# Emit raw results. -cat \$results_file >&2 - -# Emit a useful result (final throughput). -mbits=\$(grep Mbits/sec \$results_file \\ - | sed -n -e 's/^.*[[:space:]]\\([[:digit:]]\\+\\(\\.[[:digit:]]\\+\\)\\?\\)[[:space:]]*Mbits\\/sec.*/\\1/p') -client_cpu_ticks=\$(cat /proc/\$client_pid/stat \\ - | awk '{print (\$14+\$15);}') -server_cpu_ticks=\$(cat /proc/\$server_pid/stat \\ - | awk '{print (\$14+\$15);}') -ticks_per_sec=\$(getconf CLK_TCK) -client_cpu_load=\$(bc -l <<< \$client_cpu_ticks/\$ticks_per_sec/${duration}) -server_cpu_load=\$(bc -l <<< \$server_cpu_ticks/\$ticks_per_sec/${duration}) -echo \$mbits \$client_cpu_load \$server_cpu_load -EOF diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go deleted file mode 100644 index be74e4d4a..000000000 --- a/test/benchmarks/tcp/tcp_proxy.go +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Binary tcp_proxy is a simple TCP proxy. -package main - -import ( - "encoding/gob" - "flag" - "fmt" - "io" - "log" - "math/rand" - "net" - "os" - "os/signal" - "regexp" - "runtime" - "runtime/pprof" - "strconv" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" - "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" - "gvisor.dev/gvisor/pkg/tcpip/link/qdisc/fifo" - "gvisor.dev/gvisor/pkg/tcpip/network/arp" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/stack" - "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" - "gvisor.dev/gvisor/pkg/tcpip/transport/udp" -) - -var ( - port = flag.Int("port", 0, "bind port (all addresses)") - forward = flag.String("forward", "", "forwarding target") - client = flag.Bool("client", false, "use netstack for listen") - server = flag.Bool("server", false, "use netstack for dial") - - // Netstack-specific options. - mtu = flag.Int("mtu", 1280, "mtu for network stack") - addr = flag.String("addr", "", "address for tap-based netstack") - mask = flag.Int("mask", 8, "mask size for address") - iface = flag.String("iface", "", "network interface name to bind for netstack") - sack = flag.Bool("sack", false, "enable SACK support for netstack") - rack = flag.Bool("rack", false, "enable RACK in TCP") - moderateRecvBuf = flag.Bool("moderate_recv_buf", false, "enable TCP Receive Buffer Auto-tuning") - cubic = flag.Bool("cubic", false, "enable use of CUBIC congestion control for netstack") - gso = flag.Int("gso", 0, "GSO maximum size") - swgso = flag.Bool("swgso", false, "software-level GSO") - clientTCPProbeFile = flag.String("client_tcp_probe_file", "", "if specified, installs a tcp probe to dump endpoint state to the specified file.") - serverTCPProbeFile = flag.String("server_tcp_probe_file", "", "if specified, installs a tcp probe to dump endpoint state to the specified file.") - cpuprofile = flag.String("cpuprofile", "", "write cpu profile to the specified file.") - memprofile = flag.String("memprofile", "", "write memory profile to the specified file.") -) - -type impl interface { - dial(address string) (net.Conn, error) - listen(port int) (net.Listener, error) - printStats() -} - -type netImpl struct{} - -func (netImpl) dial(address string) (net.Conn, error) { - return net.Dial("tcp", address) -} - -func (netImpl) listen(port int) (net.Listener, error) { - return net.Listen("tcp", fmt.Sprintf(":%d", port)) -} - -func (netImpl) printStats() { -} - -const ( - nicID = 1 // Fixed. - bufSize = 4 << 20 // 4MB. -) - -type netstackImpl struct { - s *stack.Stack - addr tcpip.Address - mode string -} - -func setupNetwork(ifaceName string, numChannels int) (fds []int, err error) { - // Get all interfaces in the namespace. - ifaces, err := net.Interfaces() - if err != nil { - return nil, fmt.Errorf("querying interfaces: %v", err) - } - - for _, iface := range ifaces { - if iface.Name != ifaceName { - continue - } - // Create the socket. - const protocol = 0x0300 // htons(ETH_P_ALL) - fds := make([]int, numChannels) - for i := range fds { - fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, protocol) - if err != nil { - return nil, fmt.Errorf("unable to create raw socket: %v", err) - } - - // Bind to the appropriate device. - ll := unix.SockaddrLinklayer{ - Protocol: protocol, - Ifindex: iface.Index, - Pkttype: unix.PACKET_HOST, - } - if err := unix.Bind(fd, &ll); err != nil { - return nil, fmt.Errorf("unable to bind to %q: %v", iface.Name, err) - } - - // RAW Sockets by default have a very small SO_RCVBUF of 256KB, - // up it to at least 4MB to reduce packet drops. - if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, bufSize); err != nil { - return nil, fmt.Errorf("setsockopt(..., SO_RCVBUF, %v,..) = %v", bufSize, err) - } - - if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, bufSize); err != nil { - return nil, fmt.Errorf("setsockopt(..., SO_SNDBUF, %v,..) = %v", bufSize, err) - } - - if !*swgso && *gso != 0 { - if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil { - return nil, fmt.Errorf("unable to enable the PACKET_VNET_HDR option: %v", err) - } - } - fds[i] = fd - } - return fds, nil - } - return nil, fmt.Errorf("failed to find interface: %v", ifaceName) -} - -func newNetstackImpl(mode string) (impl, error) { - fds, err := setupNetwork(*iface, runtime.GOMAXPROCS(-1)) - if err != nil { - return nil, err - } - - // Parse details. - parsedAddr := tcpip.Address(net.ParseIP(*addr).To4()) - parsedDest := tcpip.Address("") // Filled in below. - parsedMask := tcpip.AddressMask("") // Filled in below. - switch *mask { - case 8: - parsedDest = tcpip.Address([]byte{parsedAddr[0], 0, 0, 0}) - parsedMask = tcpip.AddressMask([]byte{0xff, 0, 0, 0}) - case 16: - parsedDest = tcpip.Address([]byte{parsedAddr[0], parsedAddr[1], 0, 0}) - parsedMask = tcpip.AddressMask([]byte{0xff, 0xff, 0, 0}) - case 24: - parsedDest = tcpip.Address([]byte{parsedAddr[0], parsedAddr[1], parsedAddr[2], 0}) - parsedMask = tcpip.AddressMask([]byte{0xff, 0xff, 0xff, 0}) - default: - // This is just laziness; we don't expect a different mask. - return nil, fmt.Errorf("mask %d not supported", mask) - } - - // Create a new network stack. - netProtos := []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol} - transProtos := []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol} - s := stack.New(stack.Options{ - NetworkProtocols: netProtos, - TransportProtocols: transProtos, - }) - - // Generate a new mac for the eth device. - mac := make(net.HardwareAddr, 6) - rand.Read(mac) // Fill with random data. - mac[0] &^= 0x1 // Clear multicast bit. - mac[0] |= 0x2 // Set local assignment bit (IEEE802). - ep, err := fdbased.New(&fdbased.Options{ - FDs: fds, - MTU: uint32(*mtu), - EthernetHeader: true, - Address: tcpip.LinkAddress(mac), - // Enable checksum generation as we need to generate valid - // checksums for the veth device to deliver our packets to the - // peer. But we do want to disable checksum verification as veth - // devices do perform GRO and the linux host kernel may not - // regenerate valid checksums after GRO. - TXChecksumOffload: false, - RXChecksumOffload: true, - PacketDispatchMode: fdbased.RecvMMsg, - GSOMaxSize: uint32(*gso), - SoftwareGSOEnabled: *swgso, - }) - if err != nil { - return nil, fmt.Errorf("failed to create FD endpoint: %v", err) - } - if err := s.CreateNIC(nicID, fifo.New(ep, runtime.GOMAXPROCS(0), 1000)); err != nil { - return nil, fmt.Errorf("error creating NIC %q: %v", *iface, err) - } - if err := s.AddAddress(nicID, ipv4.ProtocolNumber, parsedAddr); err != nil { - return nil, fmt.Errorf("error adding IP address to %q: %v", *iface, err) - } - - subnet, err := tcpip.NewSubnet(parsedDest, parsedMask) - if err != nil { - return nil, fmt.Errorf("tcpip.Subnet(%s, %s): %s", parsedDest, parsedMask, err) - } - // Add default route; we only support - s.SetRouteTable([]tcpip.Route{ - { - Destination: subnet, - NIC: nicID, - }, - }) - - // Set protocol options. - { - opt := tcpip.TCPSACKEnabled(*sack) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return nil, fmt.Errorf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err) - } - } - - if *rack { - opt := tcpip.TCPRecovery(tcpip.TCPRACKLossDetection) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return nil, fmt.Errorf("enabling RACK failed: %v", err) - } - } - - // Enable Receive Buffer Auto-Tuning. - { - opt := tcpip.TCPModerateReceiveBufferOption(*moderateRecvBuf) - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return nil, fmt.Errorf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err) - } - } - - // Set Congestion Control to cubic if requested. - if *cubic { - opt := tcpip.CongestionControlOption("cubic") - if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil { - return nil, fmt.Errorf("SetTransportProtocolOption(%d, &%T(%s)): %s", tcp.ProtocolNumber, opt, opt, err) - } - } - - return netstackImpl{ - s: s, - addr: parsedAddr, - mode: mode, - }, nil -} - -func (n netstackImpl) dial(address string) (net.Conn, error) { - host, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - if host == "" { - // A host must be provided for the dial. - return nil, fmt.Errorf("no host provided") - } - portNumber, err := strconv.Atoi(port) - if err != nil { - return nil, err - } - addr := tcpip.FullAddress{ - NIC: nicID, - Addr: tcpip.Address(net.ParseIP(host).To4()), - Port: uint16(portNumber), - } - conn, err := gonet.DialTCP(n.s, addr, ipv4.ProtocolNumber) - if err != nil { - return nil, err - } - return conn, nil -} - -func (n netstackImpl) listen(port int) (net.Listener, error) { - addr := tcpip.FullAddress{ - NIC: nicID, - Port: uint16(port), - } - listener, err := gonet.ListenTCP(n.s, addr, ipv4.ProtocolNumber) - if err != nil { - return nil, err - } - return listener, nil -} - -var zeroFieldsRegexp = regexp.MustCompile(`\s*[a-zA-Z0-9]*:0`) - -func (n netstackImpl) printStats() { - // Don't show zero fields. - stats := zeroFieldsRegexp.ReplaceAllString(fmt.Sprintf("%+v", n.s.Stats()), "") - log.Printf("netstack %s Stats: %+v\n", n.mode, stats) -} - -// installProbe installs a TCP Probe function that will dump endpoint -// state to the specified file. It also returns a close func() that -// can be used to close the probeFile. -func (n netstackImpl) installProbe(probeFileName string) (close func()) { - // Install Probe to dump out end point state. - probeFile, err := os.Create(probeFileName) - if err != nil { - log.Fatalf("failed to create tcp_probe file %s: %v", probeFileName, err) - } - probeEncoder := gob.NewEncoder(probeFile) - // Install a TCP Probe. - n.s.AddTCPProbe(func(state stack.TCPEndpointState) { - probeEncoder.Encode(state) - }) - return func() { probeFile.Close() } -} - -func main() { - flag.Parse() - if *port == 0 { - log.Fatalf("no port provided") - } - if *forward == "" { - log.Fatalf("no forward provided") - } - // Seed the random number generator to ensure that we are given MAC addresses that don't - // for the case of the client and server stack. - rand.Seed(time.Now().UTC().UnixNano()) - - if *cpuprofile != "" { - f, err := os.Create(*cpuprofile) - if err != nil { - log.Fatal("could not create CPU profile: ", err) - } - defer func() { - if err := f.Close(); err != nil { - log.Print("error closing CPU profile: ", err) - } - }() - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal("could not start CPU profile: ", err) - } - defer pprof.StopCPUProfile() - } - - var ( - in impl - out impl - err error - ) - if *server { - in, err = newNetstackImpl("server") - if *serverTCPProbeFile != "" { - defer in.(netstackImpl).installProbe(*serverTCPProbeFile)() - } - - } else { - in = netImpl{} - } - if err != nil { - log.Fatalf("netstack error: %v", err) - } - if *client { - out, err = newNetstackImpl("client") - if *clientTCPProbeFile != "" { - defer out.(netstackImpl).installProbe(*clientTCPProbeFile)() - } - } else { - out = netImpl{} - } - if err != nil { - log.Fatalf("netstack error: %v", err) - } - - // Dial forward before binding. - var next net.Conn - for { - next, err = out.dial(*forward) - if err == nil { - break - } - time.Sleep(50 * time.Millisecond) - log.Printf("connect failed retrying: %v", err) - } - - // Bind once to the server socket. - listener, err := in.listen(*port) - if err != nil { - // Should not happen, everything must be bound by this time - // this proxy is started. - log.Fatalf("unable to listen: %v", err) - } - log.Printf("client=%v, server=%v, ready.", *client, *server) - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, unix.SIGTERM) - go func() { - <-sigs - if *cpuprofile != "" { - pprof.StopCPUProfile() - } - if *memprofile != "" { - f, err := os.Create(*memprofile) - if err != nil { - log.Fatal("could not create memory profile: ", err) - } - defer func() { - if err := f.Close(); err != nil { - log.Print("error closing memory profile: ", err) - } - }() - runtime.GC() // get up-to-date statistics - if err := pprof.WriteHeapProfile(f); err != nil { - log.Fatalf("Unable to write heap profile: %v", err) - } - } - os.Exit(0) - }() - - for { - // Forward all connections. - inConn, err := listener.Accept() - if err != nil { - // This should not happen; we are listening - // successfully. Exhausted all available FDs? - log.Fatalf("accept error: %v", err) - } - log.Printf("incoming connection established.") - - // Copy both ways. - go io.Copy(inConn, next) - go io.Copy(next, inConn) - - // Print stats every second. - go func() { - t := time.NewTicker(time.Second) - defer t.Stop() - for { - <-t.C - in.printStats() - out.printStats() - } - }() - - for { - // Dial again. - next, err = out.dial(*forward) - if err == nil { - break - } - } - } -} diff --git a/test/benchmarks/tools/BUILD b/test/benchmarks/tools/BUILD deleted file mode 100644 index 9290830d7..000000000 --- a/test/benchmarks/tools/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "tools", - testonly = 1, - srcs = [ - "ab.go", - "fio.go", - "hey.go", - "iperf.go", - "meminfo.go", - "parser_util.go", - "redis.go", - "sysbench.go", - "tools.go", - ], - visibility = ["//:sandbox"], -) - -go_test( - name = "tools_test", - size = "small", - srcs = [ - "ab_test.go", - "fio_test.go", - "hey_test.go", - "iperf_test.go", - "meminfo_test.go", - "redis_test.go", - "sysbench_test.go", - ], - library = ":tools", -) diff --git a/test/benchmarks/tools/ab.go b/test/benchmarks/tools/ab.go deleted file mode 100644 index d9abf0763..000000000 --- a/test/benchmarks/tools/ab.go +++ /dev/null @@ -1,97 +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 tools - -import ( - "fmt" - "net" - "regexp" - "strconv" - "testing" -) - -// ApacheBench is for the client application ApacheBench. -type ApacheBench struct { - Requests int - Concurrency int - Doc string - // TODO(zkoopmans): support KeepAlive and pass option to enable. -} - -// MakeCmd makes an ApacheBench command. -func (a *ApacheBench) MakeCmd(ip net.IP, port int) []string { - path := fmt.Sprintf("http://%s:%d/%s", ip, port, a.Doc) - // See apachebench (ab) for flags. - cmd := fmt.Sprintf("ab -n %d -c %d %s", a.Requests, a.Concurrency, path) - return []string{"sh", "-c", cmd} -} - -// Report parses and reports metrics from ApacheBench output. -func (a *ApacheBench) Report(b *testing.B, output string) { - // Parse and report custom metrics. - transferRate, err := a.parseTransferRate(output) - if err != nil { - b.Logf("failed to parse transferrate: %v", err) - } - b.ReportMetric(transferRate*1024, "transfer_rate_b/s") // Convert from Kb/s to b/s. - ReportCustomMetric(b, transferRate*1024, "transfer_rate" /*metric name*/, "bytes_per_second" /*unit*/) - - latency, err := a.parseLatency(output) - if err != nil { - b.Logf("failed to parse latency: %v", err) - } - b.ReportMetric(latency/1000, "mean_latency_secs") // Convert from ms to s. - ReportCustomMetric(b, latency/1000, "mean_latency" /*metric name*/, "s" /*unit*/) - - reqPerSecond, err := a.parseRequestsPerSecond(output) - if err != nil { - b.Logf("failed to parse requests per second: %v", err) - } - b.ReportMetric(reqPerSecond, "requests_per_second") - ReportCustomMetric(b, reqPerSecond, "requests_per_second" /*metric name*/, "QPS" /*unit*/) -} - -var transferRateRE = regexp.MustCompile(`Transfer rate:\s+(\d+\.?\d+?)\s+\[Kbytes/sec\]\s+received`) - -// parseTransferRate parses transfer rate from ApacheBench output. -func (a *ApacheBench) parseTransferRate(data string) (float64, error) { - match := transferRateRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("failed get bandwidth: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -var latencyRE = regexp.MustCompile(`Total:\s+\d+\s+(\d+)\s+(\d+\.?\d+?)\s+\d+\s+\d+\s`) - -// parseLatency parses latency from ApacheBench output. -func (a *ApacheBench) parseLatency(data string) (float64, error) { - match := latencyRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("failed get bandwidth: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -var requestsPerSecondRE = regexp.MustCompile(`Requests per second:\s+(\d+\.?\d+?)\s+`) - -// parseRequestsPerSecond parses requests per second from ApacheBench output. -func (a *ApacheBench) parseRequestsPerSecond(data string) (float64, error) { - match := requestsPerSecondRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("failed get bandwidth: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} diff --git a/test/benchmarks/tools/ab_test.go b/test/benchmarks/tools/ab_test.go deleted file mode 100644 index 28ee66ec1..000000000 --- a/test/benchmarks/tools/ab_test.go +++ /dev/null @@ -1,90 +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 tools - -import "testing" - -// TestApacheBench checks the ApacheBench parsers on sample output. -func TestApacheBench(t *testing.T) { - // Sample output from apachebench. - sampleData := `This is ApacheBench, Version 2.3 <$Revision: 1826891 $> -Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ -Licensed to The Apache Software Foundation, http://www.apache.org/ - -Benchmarking 10.10.10.10 (be patient).....done - - -Server Software: Apache/2.4.38 -Server Hostname: 10.10.10.10 -Server Port: 80 - -Document Path: /latin10k.txt -Document Length: 210 bytes - -Concurrency Level: 1 -Time taken for tests: 0.180 seconds -Complete requests: 100 -Failed requests: 0 -Non-2xx responses: 100 -Total transferred: 38800 bytes -HTML transferred: 21000 bytes -Requests per second: 556.44 [#/sec] (mean) -Time per request: 1.797 [ms] (mean) -Time per request: 1.797 [ms] (mean, across all concurrent requests) -Transfer rate: 210.84 [Kbytes/sec] received - -Connection Times (ms) - min mean[+/-sd] median max -Connect: 0 0 0.2 0 2 -Processing: 1 2 1.0 1 8 -Waiting: 1 1 1.0 1 7 -Total: 1 2 1.2 1 10 - -Percentage of the requests served within a certain time (ms) - 50% 1 - 66% 2 - 75% 2 - 80% 2 - 90% 2 - 95% 3 - 98% 7 - 99% 10 - 100% 10 (longest request)` - - ab := ApacheBench{} - want := 210.84 - got, err := ab.parseTransferRate(sampleData) - if err != nil { - t.Fatalf("failed to parse transfer rate with error: %v", err) - } else if got != want { - t.Fatalf("parseTransferRate got: %f, want: %f", got, want) - } - - want = 2.0 - got, err = ab.parseLatency(sampleData) - if err != nil { - t.Fatalf("failed to parse transfer rate with error: %v", err) - } else if got != want { - t.Fatalf("parseLatency got: %f, want: %f", got, want) - } - - want = 556.44 - got, err = ab.parseRequestsPerSecond(sampleData) - if err != nil { - t.Fatalf("failed to parse transfer rate with error: %v", err) - } else if got != want { - t.Fatalf("parseRequestsPerSecond got: %f, want: %f", got, want) - } -} diff --git a/test/benchmarks/tools/fio.go b/test/benchmarks/tools/fio.go deleted file mode 100644 index ea12436d2..000000000 --- a/test/benchmarks/tools/fio.go +++ /dev/null @@ -1,116 +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 tools - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "testing" -) - -// Fio makes 'fio' commands and parses their output. -type Fio struct { - Test string // test to run: read, write, randread, randwrite. - Size 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=%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("--rw=%s", f.Test)) - return cmd -} - -// Report reports metrics based on output from an 'fio' command. -func (f *Fio) Report(b *testing.B, output string) { - b.Helper() - // Parse the output and report the metrics. - isRead := strings.Contains(f.Test, "read") - bw, err := f.parseBandwidth(output, isRead) - if err != nil { - b.Fatalf("failed to parse bandwidth from %s with: %v", output, err) - } - ReportCustomMetric(b, bw, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/) - - iops, err := f.parseIOps(output, isRead) - if err != nil { - b.Fatalf("failed to parse iops from %s with: %v", output, err) - } - ReportCustomMetric(b, iops, "io_ops" /*metric name*/, "ops_per_second" /*unit*/) -} - -// parseBandwidth reports the bandwidth in b/s. -func (f *Fio) parseBandwidth(data string, isRead bool) (float64, error) { - op := "write" - if isRead { - op = "read" - } - result, err := f.parseFioJSON(data, op, "bw") - if err != nil { - return 0, err - } - return result * 1024, nil -} - -// parseIOps reports the write IO per second metric. -func (f *Fio) parseIOps(data string, isRead bool) (float64, error) { - if isRead { - return f.parseFioJSON(data, "read", "iops") - } - return f.parseFioJSON(data, "write", "iops") -} - -// fioResult is for parsing FioJSON. -type fioResult struct { - Jobs []fioJob -} - -// fioJob is for parsing FioJSON. -type fioJob map[string]json.RawMessage - -// fioMetrics is for parsing FioJSON. -type fioMetrics map[string]json.RawMessage - -// parseFioJSON parses data and grabs "op" (read or write) and "metric" -// (bw or iops) from the JSON. -func (f *Fio) parseFioJSON(data, op, metric string) (float64, error) { - var result fioResult - if err := json.Unmarshal([]byte(data), &result); err != nil { - return 0, fmt.Errorf("could not unmarshal data: %v", err) - } - - if len(result.Jobs) < 1 { - return 0, fmt.Errorf("no jobs present to parse") - } - - var metrics fioMetrics - if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil { - return 0, fmt.Errorf("could not unmarshal jobs: %v", err) - } - - if _, ok := metrics[metric]; !ok { - return 0, fmt.Errorf("no metric found for op: %s", op) - } - return strconv.ParseFloat(string(metrics[metric]), 64) -} diff --git a/test/benchmarks/tools/fio_test.go b/test/benchmarks/tools/fio_test.go deleted file mode 100644 index a98277150..000000000 --- a/test/benchmarks/tools/fio_test.go +++ /dev/null @@ -1,122 +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 tools - -import "testing" - -// TestFio checks the Fio parsers on sample output. -func TestFio(t *testing.T) { - sampleData := ` -{ - "fio version" : "fio-3.1", - "timestamp" : 1554837456, - "timestamp_ms" : 1554837456621, - "time" : "Tue Apr 9 19:17:36 2019", - "jobs" : [ - { - "jobname" : "test", - "groupid" : 0, - "error" : 0, - "eta" : 2147483647, - "elapsed" : 1, - "job options" : { - "name" : "test", - "ioengine" : "sync", - "size" : "1073741824", - "filename" : "/disk/file.dat", - "iodepth" : "4", - "bs" : "4096", - "rw" : "write" - }, - "read" : { - "io_bytes" : 0, - "io_kbytes" : 0, - "bw" : 123456, - "iops" : 1234.5678, - "runtime" : 0, - "total_ios" : 0, - "short_ios" : 0, - "bw_min" : 0, - "bw_max" : 0, - "bw_agg" : 0.000000, - "bw_mean" : 0.000000, - "bw_dev" : 0.000000, - "bw_samples" : 0, - "iops_min" : 0, - "iops_max" : 0, - "iops_mean" : 0.000000, - "iops_stddev" : 0.000000, - "iops_samples" : 0 - }, - "write" : { - "io_bytes" : 1073741824, - "io_kbytes" : 1048576, - "bw" : 1753471, - "iops" : 438367.892977, - "runtime" : 598, - "total_ios" : 262144, - "bw_min" : 1731120, - "bw_max" : 1731120, - "bw_agg" : 98.725328, - "bw_mean" : 1731120.000000, - "bw_dev" : 0.000000, - "bw_samples" : 1, - "iops_min" : 432780, - "iops_max" : 432780, - "iops_mean" : 432780.000000, - "iops_stddev" : 0.000000, - "iops_samples" : 1 - } - } - ] -} -` - fio := Fio{} - // WriteBandwidth. - got, err := fio.parseBandwidth(sampleData, false) - var want float64 = 1753471.0 * 1024 - if err != nil { - t.Fatalf("parse failed with err: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } - - // ReadBandwidth. - got, err = fio.parseBandwidth(sampleData, true) - want = 123456 * 1024 - if err != nil { - t.Fatalf("parse failed with err: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } - - // WriteIOps. - got, err = fio.parseIOps(sampleData, false) - want = 438367.892977 - if err != nil { - t.Fatalf("parse failed with err: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } - - // ReadIOps. - got, err = fio.parseIOps(sampleData, true) - want = 1234.5678 - if err != nil { - t.Fatalf("parse failed with err: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } -} diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go deleted file mode 100644 index de908feeb..000000000 --- a/test/benchmarks/tools/hey.go +++ /dev/null @@ -1,82 +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 tools - -import ( - "fmt" - "net" - "regexp" - "strconv" - "testing" -) - -// Hey is for the client application 'hey'. -type Hey struct { - Requests int // Note: requests cannot be less than concurrency. - Concurrency int - Doc string -} - -// MakeCmd returns a 'hey' command. -func (h *Hey) MakeCmd(ip net.IP, port int) []string { - 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. -func (h *Hey) Report(b *testing.B, output string) { - b.Helper() - requests, err := h.parseRequestsPerSecond(output) - if err != nil { - b.Fatalf("failed to parse requests per second: %v", err) - } - ReportCustomMetric(b, requests, "requests_per_second" /*metric name*/, "QPS" /*unit*/) - - ave, err := h.parseAverageLatency(output) - if err != nil { - b.Fatalf("failed to parse average latency: %v", err) - } - ReportCustomMetric(b, ave, "average_latency" /*metric name*/, "s" /*unit*/) -} - -var heyReqPerSecondRE = regexp.MustCompile(`Requests/sec:\s*(\d+\.?\d+?)\s+`) - -// parseRequestsPerSecond finds requests per second from 'hey' output. -func (h *Hey) parseRequestsPerSecond(data string) (float64, error) { - match := heyReqPerSecondRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("failed get bandwidth: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -var heyAverageLatencyRE = regexp.MustCompile(`Average:\s*(\d+\.?\d+?)\s+secs`) - -// parseHeyAverageLatency finds Average Latency in seconds form 'hey' output. -func (h *Hey) parseAverageLatency(data string) (float64, error) { - match := heyAverageLatencyRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("failed get average latency match%d : %s", len(match), data) - } - return strconv.ParseFloat(match[1], 64) -} diff --git a/test/benchmarks/tools/hey_test.go b/test/benchmarks/tools/hey_test.go deleted file mode 100644 index e0cab1f52..000000000 --- a/test/benchmarks/tools/hey_test.go +++ /dev/null @@ -1,81 +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 tools - -import "testing" - -// TestHey checks the Hey parsers on sample output. -func TestHey(t *testing.T) { - sampleData := ` - Summary: - Total: 2.2391 secs - Slowest: 1.6292 secs - Fastest: 0.0066 secs - Average: 0.5351 secs - Requests/sec: 89.3202 - - Total data: 841200 bytes - Size/request: 4206 bytes - - Response time histogram: - 0.007 [1] | - 0.169 [0] | - 0.331 [149] |â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– - 0.493 [0] | - 0.656 [0] | - 0.818 [0] | - 0.980 [0] | - 1.142 [0] | - 1.305 [0] | - 1.467 [49] |â– â– â– â– â– â– â– â– â– â– â– â– â– - 1.629 [1] | - - - Latency distribution: - 10% in 0.2149 secs - 25% in 0.2449 secs - 50% in 0.2703 secs - 75% in 1.3315 secs - 90% in 1.4045 secs - 95% in 1.4232 secs - 99% in 1.4362 secs - - Details (average, fastest, slowest): - DNS+dialup: 0.0002 secs, 0.0066 secs, 1.6292 secs - DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs - req write: 0.0000 secs, 0.0000 secs, 0.0012 secs - resp wait: 0.5225 secs, 0.0064 secs, 1.4346 secs - resp read: 0.0122 secs, 0.0001 secs, 0.2006 secs - - Status code distribution: - [200] 200 responses - ` - hey := Hey{} - want := 89.3202 - got, err := hey.parseRequestsPerSecond(sampleData) - if err != nil { - t.Fatalf("failed to parse request per second with: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } - - want = 0.5351 - got, err = hey.parseAverageLatency(sampleData) - if err != nil { - t.Fatalf("failed to parse average latency with: %v", err) - } else if got != want { - t.Fatalf("got: %f, want: %f", got, want) - } -} diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go deleted file mode 100644 index 8f410a9e8..000000000 --- a/test/benchmarks/tools/iperf.go +++ /dev/null @@ -1,65 +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 tools - -import ( - "fmt" - "net" - "regexp" - "strconv" - "testing" -) - -const length = 64 * 1024 - -// Iperf is for the client side of `iperf`. -type Iperf struct { - Num int // Number of bytes to send in KB. -} - -// MakeCmd returns a iperf client command. -func (i *Iperf) MakeCmd(ip net.IP, port int) []string { - return []string{ - "iperf", - "--format", "K", // Output in KBytes. - "--realtime", // Measured in realtime. - "--num", fmt.Sprintf("%dK", i.Num), // Number of bytes to send in KB. - "--length", fmt.Sprintf("%d", length), - "--client", ip.String(), - "--port", fmt.Sprintf("%d", port), - } -} - -// Report parses output from iperf client and reports metrics. -func (i *Iperf) Report(b *testing.B, output string) { - b.Helper() - // Parse bandwidth and report it. - bW, err := i.bandwidth(output) - if err != nil { - b.Fatalf("failed to parse bandwitdth from %s: %v", output, err) - } - b.SetBytes(length) // Measure Bytes/sec for b.N, although below is iperf output. - ReportCustomMetric(b, bW*1024, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/) -} - -// bandwidth parses the Bandwidth number from an iperf report. A sample is below. -func (i *Iperf) bandwidth(data string) (float64, error) { - re := regexp.MustCompile(`\[\s*\d+\][^\n]+\s+(\d+\.?\d*)\s+KBytes/sec`) - match := re.FindStringSubmatch(data) - if len(match) < 1 { - return 0, fmt.Errorf("failed get bandwidth: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} diff --git a/test/benchmarks/tools/iperf_test.go b/test/benchmarks/tools/iperf_test.go deleted file mode 100644 index 03bb30d05..000000000 --- a/test/benchmarks/tools/iperf_test.go +++ /dev/null @@ -1,34 +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 tools - -import "testing" - -// TestIperf checks the Iperf parsers on sample output. -func TestIperf(t *testing.T) { - sampleData := ` ------------------------------------------------------------- -Client connecting to 10.138.15.215, TCP port 32779 -TCP window size: 45.0 KByte (default) ------------------------------------------------------------- -[ 3] local 10.138.15.216 port 32866 connected with 10.138.15.215 port 32779 -[ ID] Interval Transfer Bandwidth -[ 3] 0.0-10.0 sec 459520 KBytes 45900 KBytes/sec -` - i := Iperf{} - bandwidth, err := i.bandwidth(sampleData) - if err != nil || bandwidth != 45900 { - t.Fatalf("failed with: %v and %f", err, bandwidth) - } -} diff --git a/test/benchmarks/tools/meminfo.go b/test/benchmarks/tools/meminfo.go deleted file mode 100644 index b5786fe11..000000000 --- a/test/benchmarks/tools/meminfo.go +++ /dev/null @@ -1,60 +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 tools - -import ( - "fmt" - "regexp" - "strconv" - "testing" -) - -// Meminfo wraps measurements of MemAvailable using /proc/meminfo. -type Meminfo struct { -} - -// MakeCmd returns a command for checking meminfo. -func (*Meminfo) MakeCmd() (string, []string) { - return "cat", []string{"/proc/meminfo"} -} - -// Report takes two reads of meminfo, parses them, and reports the difference -// divided by b.N. -func (*Meminfo) Report(b *testing.B, before, after string) { - b.Helper() - - beforeVal, err := parseMemAvailable(before) - if err != nil { - b.Fatalf("could not parse before value %s: %v", before, err) - } - - afterVal, err := parseMemAvailable(after) - if err != nil { - b.Fatalf("could not parse before value %s: %v", before, err) - } - val := 1024 * ((beforeVal - afterVal) / float64(b.N)) - ReportCustomMetric(b, val, "average_container_size" /*metric name*/, "bytes" /*units*/) -} - -var memInfoRE = regexp.MustCompile(`MemAvailable:\s*(\d+)\skB\n`) - -// parseMemAvailable grabs the MemAvailable number from /proc/meminfo. -func parseMemAvailable(data string) (float64, error) { - match := memInfoRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0, fmt.Errorf("couldn't find MemAvailable in %s", data) - } - return strconv.ParseFloat(match[1], 64) -} diff --git a/test/benchmarks/tools/meminfo_test.go b/test/benchmarks/tools/meminfo_test.go deleted file mode 100644 index ba803540f..000000000 --- a/test/benchmarks/tools/meminfo_test.go +++ /dev/null @@ -1,84 +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 tools - -import ( - "testing" -) - -// TestMeminfo checks the Meminfo parser on sample output. -func TestMeminfo(t *testing.T) { - sampleData := ` -MemTotal: 16337408 kB -MemFree: 3742696 kB -MemAvailable: 9319948 kB -Buffers: 1433884 kB -Cached: 4607036 kB -SwapCached: 45284 kB -Active: 8288376 kB -Inactive: 2685928 kB -Active(anon): 4724912 kB -Inactive(anon): 1047940 kB -Active(file): 3563464 kB -Inactive(file): 1637988 kB -Unevictable: 326940 kB -Mlocked: 48 kB -SwapTotal: 33292284 kB -SwapFree: 32865736 kB -Dirty: 708 kB -Writeback: 0 kB -AnonPages: 4304204 kB -Mapped: 975424 kB -Shmem: 910292 kB -KReclaimable: 744532 kB -Slab: 1058448 kB -SReclaimable: 744532 kB -SUnreclaim: 313916 kB -KernelStack: 25188 kB -PageTables: 65300 kB -NFS_Unstable: 0 kB -Bounce: 0 kB -WritebackTmp: 0 kB -CommitLimit: 41460988 kB -Committed_AS: 22859492 kB -VmallocTotal: 34359738367 kB -VmallocUsed: 63088 kB -VmallocChunk: 0 kB -Percpu: 9248 kB -HardwareCorrupted: 0 kB -AnonHugePages: 786432 kB -ShmemHugePages: 0 kB -ShmemPmdMapped: 0 kB -FileHugePages: 0 kB -FilePmdMapped: 0 kB -HugePages_Total: 0 -HugePages_Free: 0 -HugePages_Rsvd: 0 -HugePages_Surp: 0 -Hugepagesize: 2048 kB -Hugetlb: 0 kB -DirectMap4k: 5408532 kB -DirectMap2M: 11241472 kB -DirectMap1G: 1048576 kB -` - want := 9319948.0 - got, err := parseMemAvailable(sampleData) - if err != nil { - t.Fatalf("parseMemAvailable failed: %v", err) - } - if got != want { - t.Fatalf("parseMemAvailable got %f, want %f", got, want) - } -} diff --git a/test/benchmarks/tools/parser_util.go b/test/benchmarks/tools/parser_util.go deleted file mode 100644 index a4555c7dd..000000000 --- a/test/benchmarks/tools/parser_util.go +++ /dev/null @@ -1,101 +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 tools - -import ( - "fmt" - "regexp" - "strconv" - "strings" - "testing" -) - -// Parameter is a test parameter. -type Parameter struct { - Name string - Value string -} - -// Output is parsed and split by these values. Make them illegal in input methods. -// We are constrained on what characters these can be by 1) docker's allowable -// container names, 2) golang allowable benchmark names, and 3) golangs allowable -// charecters in b.ReportMetric calls. -var illegalChars = regexp.MustCompile(`[/\.]`) - -// ParametersToName joins parameters into a string format for parsing. -// It is meant to be used for t.Run() calls in benchmark tools. -func ParametersToName(params ...Parameter) (string, error) { - var strs []string - for _, param := range params { - if illegalChars.MatchString(param.Name) || illegalChars.MatchString(param.Value) { - return "", fmt.Errorf("params Name: %q and Value: %q cannot container '.' or '/'", param.Name, param.Value) - } - strs = append(strs, strings.Join([]string{param.Name, param.Value}, ".")) - } - return strings.Join(strs, "/"), nil -} - -// NameToParameters parses the string created by ParametersToName and returns -// it as a set of Parameters. -// Example: BenchmarkRuby/server_threads.1/doc_size.16KB-6 -// The parameter part of this benchmark is: -// "server_threads.1/doc_size.16KB" (BenchmarkRuby is the name, and 6 is GOMAXPROCS) -// This function will return a slice with two parameters -> -// {Name: server_threads, Value: 1}, {Name: doc_size, Value: 16KB} -func NameToParameters(name string) ([]*Parameter, error) { - var params []*Parameter - for _, cond := range strings.Split(name, "/") { - cs := strings.Split(cond, ".") - switch len(cs) { - case 1: - params = append(params, &Parameter{Name: cond, Value: cond}) - case 2: - params = append(params, &Parameter{Name: cs[0], Value: cs[1]}) - default: - return nil, fmt.Errorf("failed to parse param: %s", cond) - } - } - return params, nil -} - -// ReportCustomMetric reports a metric in a set format for parsing. -func ReportCustomMetric(b *testing.B, value float64, name, unit string) { - if illegalChars.MatchString(name) || illegalChars.MatchString(unit) { - b.Fatalf("name: %q and unit: %q cannot contain '/' or '.'", name, unit) - } - nameUnit := strings.Join([]string{name, unit}, ".") - b.ReportMetric(value, nameUnit) -} - -// Metric holds metric data parsed from a string based on the format -// ReportMetric. -type Metric struct { - Name string - Unit string - Sample float64 -} - -// ParseCustomMetric parses a metric reported with ReportCustomMetric. -func ParseCustomMetric(value, metric string) (*Metric, error) { - sample, err := strconv.ParseFloat(value, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse value: %v", err) - } - nameUnit := strings.Split(metric, ".") - if len(nameUnit) != 2 { - return nil, fmt.Errorf("failed to parse metric: %s", metric) - } - return &Metric{Name: nameUnit[0], Unit: nameUnit[1], Sample: sample}, nil -} diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go deleted file mode 100644 index 12fdbc7cc..000000000 --- a/test/benchmarks/tools/redis.go +++ /dev/null @@ -1,74 +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 tools - -import ( - "fmt" - "net" - "regexp" - "strconv" - "testing" -) - -// Redis is for the client 'redis-benchmark'. -type Redis struct { - Operation string -} - -// MakeCmd returns a redis-benchmark client command. -func (r *Redis) MakeCmd(ip net.IP, port, 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 []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 []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. -func (r *Redis) Report(b *testing.B, output string) { - b.Helper() - result, err := r.parseOperation(output) - if err != nil { - b.Fatalf("parsing result %s failed with err: %v", output, err) - } - ReportCustomMetric(b, result, r.Operation /*metric_name*/, "QPS" /*unit*/) -} - -// parseOperation grabs the metric operations per second from redis-benchmark output. -func (r *Redis) parseOperation(data string) (float64, error) { - re := regexp.MustCompile(fmt.Sprintf(`"%s( .*)?","(\d*\.\d*)"`, r.Operation)) - match := re.FindStringSubmatch(data) - if len(match) < 3 { - return 0.0, fmt.Errorf("could not find %s in %s", r.Operation, data) - } - return strconv.ParseFloat(match[2], 64) -} diff --git a/test/benchmarks/tools/redis_test.go b/test/benchmarks/tools/redis_test.go deleted file mode 100644 index 4bafda66f..000000000 --- a/test/benchmarks/tools/redis_test.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 tools - -import ( - "testing" -) - -// TestRedis checks the Redis parsers on sample output. -func TestRedis(t *testing.T) { - sampleData := ` - "PING_INLINE","48661.80" - "PING_BULK","50301.81" - "SET","48923.68" - "GET","49382.71" - "INCR","49975.02" - "LPUSH","49875.31" - "RPUSH","50276.52" - "LPOP","50327.12" - "RPOP","50556.12" - "SADD","49504.95" - "HSET","49504.95" - "SPOP","50025.02" - "LPUSH (needed to benchmark LRANGE)","48875.86" - "LRANGE_100 (first 100 elements)","33955.86" - "LRANGE_300 (first 300 elements)","16550.81"// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tools - - "LRANGE_500 (first 450 elements)","13653.74" - "LRANGE_600 (first 600 elements)","11219.57" - "MSET (10 keys)","44682.75" - ` - wants := map[string]float64{ - "PING_INLINE": 48661.80, - "PING_BULK": 50301.81, - "SET": 48923.68, - "GET": 49382.71, - "INCR": 49975.02, - "LPUSH": 49875.31, - "RPUSH": 50276.52, - "LPOP": 50327.12, - "RPOP": 50556.12, - "SADD": 49504.95, - "HSET": 49504.95, - "SPOP": 50025.02, - "LRANGE_100": 33955.86, - "LRANGE_300": 16550.81, - "LRANGE_500": 13653.74, - "LRANGE_600": 11219.57, - "MSET": 44682.75, - } - for op, want := range wants { - redis := Redis{ - Operation: op, - } - if got, err := redis.parseOperation(sampleData); err != nil { - t.Fatalf("failed to parse %s: %v", op, err) - } else if want != got { - t.Fatalf("wanted %f for op %s, got %f", want, op, got) - } - } -} diff --git a/test/benchmarks/tools/sysbench.go b/test/benchmarks/tools/sysbench.go deleted file mode 100644 index 350f8ec98..000000000 --- a/test/benchmarks/tools/sysbench.go +++ /dev/null @@ -1,236 +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 tools - -import ( - "fmt" - "regexp" - "strconv" - "testing" -) - -// Sysbench represents a 'sysbench' command. -type Sysbench interface { - // 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 is the number of threads for the test. - Threads int -} - -// baseFlags returns top level flags. -func (s *SysbenchBase) baseFlags(b *testing.B, useEvents bool) []string { - var ret []string - if s.Threads > 0 { - ret = append(ret, fmt.Sprintf("--threads=%d", s.Threads)) - } - ret = append(ret, "--time=0") // Ensure other mechanism is used. - if useEvents { - 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 { - SysbenchBase -} - -// MakeCmd makes commands for SysbenchCPU. -func (s *SysbenchCPU) MakeCmd(b *testing.B) []string { - cmd := []string{"sysbench"} - cmd = append(cmd, s.baseFlags(b, true /* useEvents */)...) - cmd = append(cmd, "cpu", "run") - return cmd -} - -// Report reports the relevant metrics for SysbenchCPU. -func (s *SysbenchCPU) Report(b *testing.B, output string) { - b.Helper() - result, err := s.parseEvents(output) - if err != nil { - b.Fatalf("parsing CPU events from %s failed: %v", output, err) - } - ReportCustomMetric(b, result, "cpu_events" /*metric name*/, "events_per_second" /*unit*/) -} - -var cpuEventsPerSecondRE = regexp.MustCompile(`events per second:\s*(\d*.?\d*)\n`) - -// parseEvents parses cpu events per second. -func (s *SysbenchCPU) parseEvents(data string) (float64, error) { - match := cpuEventsPerSecondRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0.0, fmt.Errorf("could not find events per second: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -// SysbenchMemory is for 'sysbench [FLAGS] memory run' and holds Memory specific arguments. -type SysbenchMemory struct { - SysbenchBase - BlockSize int // size of test memory block in megabytes [1]. - 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]. - AccessMode string // access mode {seq, rnd} [seq]. -} - -// MakeCmd makes commands for SysbenchMemory. -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(b *testing.B) []string { - cmd := s.baseFlags(b, false /* useEvents */) - if s.BlockSize != 0 { - cmd = append(cmd, fmt.Sprintf("--memory-block-size=%dM", s.BlockSize)) - } - if s.Scope != "" { - cmd = append(cmd, fmt.Sprintf("--memory-scope=%s", s.Scope)) - } - if s.HugeTLB { - cmd = append(cmd, "--memory-hugetlb=on") - } - if s.OperationType != "" { - cmd = append(cmd, fmt.Sprintf("--memory-oper=%s", s.OperationType)) - } - if s.AccessMode != "" { - cmd = append(cmd, fmt.Sprintf("--memory-access-mode=%s", s.AccessMode)) - } - // Sysbench ignores events for memory tests, and uses the total - // size parameter to determine when the test is done. We scale - // with this instead. - cmd = append(cmd, fmt.Sprintf("--memory-total-size=%dG", b.N)) - return cmd -} - -// Report reports the relevant metrics for SysbenchMemory. -func (s *SysbenchMemory) Report(b *testing.B, output string) { - b.Helper() - result, err := s.parseOperations(output) - if err != nil { - b.Fatalf("parsing result %s failed with err: %v", output, err) - } - ReportCustomMetric(b, result, "memory_operations" /*metric name*/, "ops_per_second" /*unit*/) -} - -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) { - match := memoryOperationsRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0.0, fmt.Errorf("couldn't find memory operations per second: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -// SysbenchMutex is for 'sysbench [FLAGS] mutex run' and holds Mutex specific arguments. -type SysbenchMutex struct { - SysbenchBase - Num int // total size of mutex array [4096]. - Loops int // number of loops to do outside mutex lock [10000]. -} - -// MakeCmd makes commands for SysbenchMutex. -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(b *testing.B) []string { - var cmd []string - cmd = append(cmd, s.baseFlags(b, false /* useEvents */)...) - if s.Num > 0 { - cmd = append(cmd, fmt.Sprintf("--mutex-num=%d", s.Num)) - } - if s.Loops > 0 { - cmd = append(cmd, fmt.Sprintf("--mutex-loops=%d", s.Loops)) - } - // Sysbench does not respect --events for mutex tests. From [1]: - // "Here --time or --events are completely ignored. Sysbench always - // runs one event per thread." - // [1] https://tomfern.com/posts/sysbench-guide-1 - cmd = append(cmd, fmt.Sprintf("--mutex-locks=%d", b.N)) - return cmd -} - -// Report parses and reports relevant sysbench mutex metrics. -func (s *SysbenchMutex) Report(b *testing.B, output string) { - b.Helper() - - result, err := s.parseExecutionTime(output) - if err != nil { - b.Fatalf("parsing result %s failed with err: %v", output, err) - } - ReportCustomMetric(b, result, "average_execution_time" /*metric name*/, "s" /*unit*/) - - result, err = s.parseDeviation(output) - if err != nil { - b.Fatalf("parsing result %s failed with err: %v", output, err) - } - ReportCustomMetric(b, result, "stddev_execution_time" /*metric name*/, "s" /*unit*/) - - result, err = s.parseLatency(output) - if err != nil { - b.Fatalf("parsing result %s failed with err: %v", output, err) - } - ReportCustomMetric(b, result/1000, "average_latency" /*metric name*/, "s" /*unit*/) -} - -var executionTimeRE = regexp.MustCompile(`execution time \(avg/stddev\):\s*(\d*.?\d*)/(\d*.?\d*)`) - -// parseExecutionTime parses threads fairness average execution time from sysbench output. -func (s *SysbenchMutex) parseExecutionTime(data string) (float64, error) { - match := executionTimeRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0.0, fmt.Errorf("could not find execution time average: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} - -// parseDeviation parses threads fairness stddev time from sysbench output. -func (s *SysbenchMutex) parseDeviation(data string) (float64, error) { - match := executionTimeRE.FindStringSubmatch(data) - if len(match) < 3 { - return 0.0, fmt.Errorf("could not find execution time deviation: %s", data) - } - return strconv.ParseFloat(match[2], 64) -} - -var averageLatencyRE = regexp.MustCompile(`avg:[^\n^\d]*(\d*\.?\d*)`) - -// parseLatency parses latency from sysbench output. -func (s *SysbenchMutex) parseLatency(data string) (float64, error) { - match := averageLatencyRE.FindStringSubmatch(data) - if len(match) < 2 { - return 0.0, fmt.Errorf("could not find average latency: %s", data) - } - return strconv.ParseFloat(match[1], 64) -} diff --git a/test/benchmarks/tools/sysbench_test.go b/test/benchmarks/tools/sysbench_test.go deleted file mode 100644 index 850d1939e..000000000 --- a/test/benchmarks/tools/sysbench_test.go +++ /dev/null @@ -1,169 +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 tools - -import ( - "testing" -) - -// TestSysbenchCpu tests parses on sample 'sysbench cpu' output. -func TestSysbenchCpu(t *testing.T) { - sampleData := ` -sysbench 1.0.11 (using system LuaJIT 2.1.0-beta3) - -Running the test with following options: -Number of threads: 8 -Initializing random number generator from current time - - -Prime numbers limit: 10000 - -Initializing worker threads... - -Threads started! - -CPU speed: - events per second: 9093.38 - -General statistics: - total time: 10.0007s - total number of events: 90949 - -Latency (ms): - min: 0.64 - avg: 0.88 - max: 24.65 - 95th percentile: 1.55 - sum: 79936.91 - -Threads fairness: - events (avg/stddev): 11368.6250/831.38 - execution time (avg/stddev): 9.9921/0.01 -` - sysbench := SysbenchCPU{} - want := 9093.38 - if got, err := sysbench.parseEvents(sampleData); err != nil { - t.Fatalf("parse cpu events failed: %v", err) - } else if want != got { - t.Fatalf("got: %f want: %f", got, want) - } -} - -// TestSysbenchMemory tests parsers on sample 'sysbench memory' output. -func TestSysbenchMemory(t *testing.T) { - sampleData := ` -sysbench 1.0.11 (using system LuaJIT 2.1.0-beta3) - -Running the test with following options: -Number of threads: 8 -Initializing random number generator from current time - - -Running memory speed test with the following options: - block size: 1KiB - total size: 102400MiB - operation: write - scope: global - -Initializing worker threads... - -Threads started! - -Total operations: 47999046 (9597428.64 per second) - -46874.07 MiB transferred (9372.49 MiB/sec) - - -General statistics: - total time: 5.0001s - total number of events: 47999046 - -Latency (ms): - min: 0.00 - avg: 0.00 - max: 0.21 - 95th percentile: 0.00 - sum: 33165.91 - -Threads fairness: - events (avg/stddev): 5999880.7500/111242.52 - execution time (avg/stddev): 4.1457/0.09 -` - sysbench := SysbenchMemory{} - want := 9597428.64 - if got, err := sysbench.parseOperations(sampleData); err != nil { - t.Fatalf("parse memory ops failed: %v", err) - } else if want != got { - t.Fatalf("got: %f want: %f", got, want) - } -} - -// TestSysbenchMutex tests parsers on sample 'sysbench mutex' output. -func TestSysbenchMutex(t *testing.T) { - sampleData := ` -sysbench 1.0.11 (using system LuaJIT 2.1.0-beta3) - -The 'mutex' test requires a command argument. See 'sysbench mutex help' -root@ec078132e294:/# sysbench mutex --threads=8 run -sysbench 1.0.11 (using system LuaJIT 2.1.0-beta3) - -Running the test with following options: -Number of threads: 8 -Initializing random number generator from current time - - -Initializing worker threads... - -Threads started! - - -General statistics: - total time: 0.2320s - total number of events: 8 - -Latency (ms): - min: 152.35 - avg: 192.48 - max: 231.41 - 95th percentile: 231.53 - sum: 1539.83 - -Threads fairness: - events (avg/stddev): 1.0000/0.00 - execution time (avg/stddev): 0.1925/0.04 -` - - sysbench := SysbenchMutex{} - want := .1925 - if got, err := sysbench.parseExecutionTime(sampleData); err != nil { - t.Fatalf("parse mutex time failed: %v", err) - } else if want != got { - t.Fatalf("got: %f want: %f", got, want) - } - - want = 0.04 - if got, err := sysbench.parseDeviation(sampleData); err != nil { - t.Fatalf("parse mutex deviation failed: %v", err) - } else if want != got { - t.Fatalf("got: %f want: %f", got, want) - } - - want = 192.48 - if got, err := sysbench.parseLatency(sampleData); err != nil { - t.Fatalf("parse mutex time failed: %v", err) - } else if want != got { - t.Fatalf("got: %f want: %f", got, want) - } -} diff --git a/test/benchmarks/tools/tools.go b/test/benchmarks/tools/tools.go deleted file mode 100644 index eb61c0136..000000000 --- a/test/benchmarks/tools/tools.go +++ /dev/null @@ -1,17 +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 tools holds tooling to couple command formatting and output parsers -// together. -package tools diff --git a/test/cmd/test_app/BUILD b/test/cmd/test_app/BUILD deleted file mode 100644 index 98ba5a3d9..000000000 --- a/test/cmd/test_app/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "test_app", - testonly = 1, - srcs = [ - "fds.go", - "test_app.go", - ], - pure = True, - visibility = ["//runsc/container:__pkg__"], - deps = [ - "//pkg/test/testutil", - "//pkg/unet", - "//runsc/flag", - "@com_github_google_subcommands//:go_default_library", - "@com_github_kr_pty//:go_default_library", - ], -) diff --git a/test/cmd/test_app/fds.go b/test/cmd/test_app/fds.go deleted file mode 100644 index 9b5f7231a..000000000 --- a/test/cmd/test_app/fds.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "io" - "io/ioutil" - "log" - "os" - "time" - - "github.com/google/subcommands" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/runsc/flag" -) - -const fileContents = "foobarbaz" - -// fdSender will open a file and send the FD over a unix domain socket. -type fdSender struct { - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*fdSender) Name() string { - return "fd_sender" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*fdSender) Synopsis() string { - return "creates a file and sends the FD over the socket" -} - -// Usage implements subcommands.Command.Usage. -func (*fdSender) Usage() string { - return "fd_sender <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (fds *fdSender) SetFlags(f *flag.FlagSet) { - f.StringVar(&fds.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (fds *fdSender) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if fds.socketPath == "" { - log.Fatalf("socket flag must be set") - } - - dir, err := ioutil.TempDir("", "") - if err != nil { - log.Fatalf("TempDir failed: %v", err) - } - - fileToSend, err := ioutil.TempFile(dir, "") - if err != nil { - log.Fatalf("TempFile failed: %v", err) - } - defer fileToSend.Close() - - if _, err := fileToSend.WriteString(fileContents); err != nil { - log.Fatalf("Write(%q) failed: %v", fileContents, err) - } - - // Receiver may not be started yet, so try connecting in a poll loop. - var s *unet.Socket - if err := testutil.Poll(func() error { - var err error - s, err = unet.Connect(fds.socketPath, true /* SEQPACKET, so we can send empty message with FD */) - return err - }, 10*time.Second); err != nil { - log.Fatalf("Error connecting to socket %q: %v", fds.socketPath, err) - } - defer s.Close() - - w := s.Writer(true) - w.ControlMessage.PackFDs(int(fileToSend.Fd())) - if _, err := w.WriteVec([][]byte{{'a'}}); err != nil { - log.Fatalf("Error sending FD %q over socket %q: %v", fileToSend.Fd(), fds.socketPath, err) - } - - log.Print("FD SENDER exiting successfully") - return subcommands.ExitSuccess -} - -// fdReceiver receives an FD from a unix domain socket and does things to it. -type fdReceiver struct { - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*fdReceiver) Name() string { - return "fd_receiver" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*fdReceiver) Synopsis() string { - return "reads an FD from a unix socket, and then does things to it" -} - -// Usage implements subcommands.Command.Usage. -func (*fdReceiver) Usage() string { - return "fd_receiver <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (fdr *fdReceiver) SetFlags(f *flag.FlagSet) { - f.StringVar(&fdr.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if fdr.socketPath == "" { - log.Fatalf("Flags cannot be empty, given: socket: %q", fdr.socketPath) - } - - ss, err := unet.BindAndListen(fdr.socketPath, true /* packet */) - if err != nil { - log.Fatalf("BindAndListen(%q) failed: %v", fdr.socketPath, err) - } - defer ss.Close() - - var s *unet.Socket - c := make(chan error, 1) - go func() { - var err error - s, err = ss.Accept() - c <- err - }() - - select { - case err := <-c: - if err != nil { - log.Fatalf("Accept() failed: %v", err) - } - case <-time.After(10 * time.Second): - log.Fatalf("Timeout waiting for accept") - } - - r := s.Reader(true) - r.EnableFDs(1) - b := [][]byte{{'a'}} - if n, err := r.ReadVec(b); n != 1 || err != nil { - log.Fatalf("ReadVec got n=%d err %v (wanted 0, nil)", n, err) - } - - fds, err := r.ExtractFDs() - if err != nil { - log.Fatalf("ExtractFD() got err %v", err) - } - if len(fds) != 1 { - log.Fatalf("ExtractFD() got %d FDs, wanted 1", len(fds)) - } - fd := fds[0] - - file := os.NewFile(uintptr(fd), "received file") - defer file.Close() - if _, err := file.Seek(0, io.SeekStart); err != nil { - log.Fatalf("Error from seek(0, 0): %v", err) - } - - got, err := ioutil.ReadAll(file) - if err != nil { - log.Fatalf("ReadAll failed: %v", err) - } - if string(got) != fileContents { - log.Fatalf("ReadAll got %q want %q", string(got), fileContents) - } - - log.Print("FD RECEIVER exiting successfully") - return subcommands.ExitSuccess -} diff --git a/test/cmd/test_app/test_app.go b/test/cmd/test_app/test_app.go deleted file mode 100644 index 3ba4f38f8..000000000 --- a/test/cmd/test_app/test_app.go +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Binary test_app is like a swiss knife for tests that need to run anything -// inside the sandbox. New functionality can be added with new commands. -package main - -import ( - "context" - "fmt" - "io" - "io/ioutil" - "log" - "net" - "os" - "os/exec" - "regexp" - "strconv" - sys "syscall" - "time" - - "github.com/google/subcommands" - "github.com/kr/pty" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/flag" -) - -func main() { - subcommands.Register(subcommands.HelpCommand(), "") - subcommands.Register(subcommands.FlagsCommand(), "") - subcommands.Register(new(capability), "") - subcommands.Register(new(fdReceiver), "") - subcommands.Register(new(fdSender), "") - subcommands.Register(new(forkBomb), "") - subcommands.Register(new(ptyRunner), "") - subcommands.Register(new(reaper), "") - subcommands.Register(new(syscall), "") - subcommands.Register(new(taskTree), "") - subcommands.Register(new(uds), "") - - flag.Parse() - - exitCode := subcommands.Execute(context.Background()) - os.Exit(int(exitCode)) -} - -type uds struct { - fileName string - socketPath string -} - -// Name implements subcommands.Command.Name. -func (*uds) Name() string { - return "uds" -} - -// Synopsis implements subcommands.Command.Synopsys. -func (*uds) Synopsis() string { - return "creates unix domain socket client and server. Client sends a contant flow of sequential numbers. Server prints them to --file" -} - -// Usage implements subcommands.Command.Usage. -func (*uds) Usage() string { - return "uds <flags>" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (c *uds) SetFlags(f *flag.FlagSet) { - f.StringVar(&c.fileName, "file", "", "name of output file") - f.StringVar(&c.socketPath, "socket", "", "path to socket") -} - -// Execute implements subcommands.Command.Execute. -func (c *uds) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if c.fileName == "" || c.socketPath == "" { - log.Fatalf("Flags cannot be empty, given: fileName: %q, socketPath: %q", c.fileName, c.socketPath) - return subcommands.ExitFailure - } - outputFile, err := os.OpenFile(c.fileName, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - log.Fatal("error opening output file:", err) - } - - defer os.Remove(c.socketPath) - - listener, err := net.Listen("unix", c.socketPath) - if err != nil { - log.Fatalf("error listening on socket %q: %v", c.socketPath, err) - } - - go server(listener, outputFile) - for i := 0; ; i++ { - conn, err := net.Dial("unix", c.socketPath) - if err != nil { - log.Fatal("error dialing:", err) - } - if _, err := conn.Write([]byte(strconv.Itoa(i))); err != nil { - log.Fatal("error writing:", err) - } - conn.Close() - time.Sleep(100 * time.Millisecond) - } -} - -func server(listener net.Listener, out *os.File) { - buf := make([]byte, 16) - - for { - c, err := listener.Accept() - if err != nil { - log.Fatal("error accepting connection:", err) - } - nr, err := c.Read(buf) - if err != nil { - log.Fatal("error reading from buf:", err) - } - data := buf[0:nr] - fmt.Fprint(out, string(data)+"\n") - } -} - -type taskTree struct { - depth int - width int - pause bool -} - -// Name implements subcommands.Command. -func (*taskTree) Name() string { - return "task-tree" -} - -// Synopsis implements subcommands.Command. -func (*taskTree) Synopsis() string { - return "creates a tree of tasks" -} - -// Usage implements subcommands.Command. -func (*taskTree) Usage() string { - return "task-tree <flags>" -} - -// SetFlags implements subcommands.Command. -func (c *taskTree) SetFlags(f *flag.FlagSet) { - f.IntVar(&c.depth, "depth", 1, "number of levels to create") - f.IntVar(&c.width, "width", 1, "number of tasks at each level") - f.BoolVar(&c.pause, "pause", false, "whether the tasks should pause perpetually") -} - -// Execute implements subcommands.Command. -func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - stop := testutil.StartReaper() - defer stop() - - if c.depth == 0 { - log.Printf("Child sleeping, PID: %d\n", os.Getpid()) - select {} - } - log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid()) - - var cmds []*exec.Cmd - for i := 0; i < c.width; i++ { - cmd := exec.Command( - "/proc/self/exe", c.Name(), - "--depth", strconv.Itoa(c.depth-1), - "--width", strconv.Itoa(c.width), - "--pause", strconv.FormatBool(c.pause)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - log.Fatal("failed to call self:", err) - } - cmds = append(cmds, cmd) - } - - for _, c := range cmds { - c.Wait() - } - - if c.pause { - select {} - } - - return subcommands.ExitSuccess -} - -type forkBomb struct { - delay time.Duration -} - -// Name implements subcommands.Command. -func (*forkBomb) Name() string { - return "fork-bomb" -} - -// Synopsis implements subcommands.Command. -func (*forkBomb) Synopsis() string { - return "creates child process until the end of times" -} - -// Usage implements subcommands.Command. -func (*forkBomb) Usage() string { - return "fork-bomb <flags>" -} - -// SetFlags implements subcommands.Command. -func (c *forkBomb) SetFlags(f *flag.FlagSet) { - f.DurationVar(&c.delay, "delay", 100*time.Millisecond, "amount of time to delay creation of child") -} - -// Execute implements subcommands.Command. -func (c *forkBomb) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - time.Sleep(c.delay) - - cmd := exec.Command("/proc/self/exe", c.Name()) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - log.Fatal("failed to call self:", err) - } - return subcommands.ExitSuccess -} - -type reaper struct{} - -// Name implements subcommands.Command. -func (*reaper) Name() string { - return "reaper" -} - -// Synopsis implements subcommands.Command. -func (*reaper) Synopsis() string { - return "reaps all children in a loop" -} - -// Usage implements subcommands.Command. -func (*reaper) Usage() string { - return "reaper <flags>" -} - -// SetFlags implements subcommands.Command. -func (*reaper) SetFlags(*flag.FlagSet) {} - -// Execute implements subcommands.Command. -func (c *reaper) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - stop := testutil.StartReaper() - defer stop() - select {} -} - -type syscall struct { - sysno uint64 -} - -// Name implements subcommands.Command. -func (*syscall) Name() string { - return "syscall" -} - -// Synopsis implements subcommands.Command. -func (*syscall) Synopsis() string { - return "syscall makes a syscall" -} - -// Usage implements subcommands.Command. -func (*syscall) Usage() string { - return "syscall <flags>" -} - -// SetFlags implements subcommands.Command. -func (s *syscall) SetFlags(f *flag.FlagSet) { - f.Uint64Var(&s.sysno, "syscall", 0, "syscall to call") -} - -// Execute implements subcommands.Command. -func (s *syscall) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if _, _, errno := sys.Syscall(uintptr(s.sysno), 0, 0, 0); errno != 0 { - fmt.Printf("syscall(%d, 0, 0...) failed: %v\n", s.sysno, errno) - } else { - fmt.Printf("syscall(%d, 0, 0...) success\n", s.sysno) - } - return subcommands.ExitSuccess -} - -type capability struct { - enabled uint64 - disabled uint64 -} - -// Name implements subcommands.Command. -func (*capability) Name() string { - return "capability" -} - -// Synopsis implements subcommands.Command. -func (*capability) Synopsis() string { - return "checks if effective capabilities are set/unset" -} - -// Usage implements subcommands.Command. -func (*capability) Usage() string { - return "capability [--enabled=number] [--disabled=number]" -} - -// SetFlags implements subcommands.Command. -func (c *capability) SetFlags(f *flag.FlagSet) { - f.Uint64Var(&c.enabled, "enabled", 0, "") - f.Uint64Var(&c.disabled, "disabled", 0, "") -} - -// Execute implements subcommands.Command. -func (c *capability) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - if c.enabled == 0 && c.disabled == 0 { - fmt.Println("One of the flags must be set") - return subcommands.ExitUsageError - } - - status, err := ioutil.ReadFile("/proc/self/status") - if err != nil { - fmt.Printf("Error reading %q: %v\n", "proc/self/status", err) - return subcommands.ExitFailure - } - re := regexp.MustCompile("CapEff:\t([0-9a-f]+)\n") - matches := re.FindStringSubmatch(string(status)) - if matches == nil || len(matches) != 2 { - fmt.Printf("Effective capabilities not found in\n%s\n", status) - return subcommands.ExitFailure - } - caps, err := strconv.ParseUint(matches[1], 16, 64) - if err != nil { - fmt.Printf("failed to convert capabilities %q: %v\n", matches[1], err) - return subcommands.ExitFailure - } - - if c.enabled != 0 && (caps&c.enabled) != c.enabled { - fmt.Printf("Missing capabilities, want: %#x: got: %#x\n", c.enabled, caps) - return subcommands.ExitFailure - } - if c.disabled != 0 && (caps&c.disabled) != 0 { - fmt.Printf("Extra capabilities found, dont_want: %#x: got: %#x\n", c.disabled, caps) - return subcommands.ExitFailure - } - - return subcommands.ExitSuccess -} - -type ptyRunner struct{} - -// Name implements subcommands.Command. -func (*ptyRunner) Name() string { - return "pty-runner" -} - -// Synopsis implements subcommands.Command. -func (*ptyRunner) Synopsis() string { - return "runs the given command with an open pty terminal" -} - -// Usage implements subcommands.Command. -func (*ptyRunner) Usage() string { - return "pty-runner [command]" -} - -// SetFlags implements subcommands.Command.SetFlags. -func (*ptyRunner) SetFlags(f *flag.FlagSet) {} - -// Execute implements subcommands.Command. -func (*ptyRunner) Execute(_ context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - c := exec.Command(fs.Args()[0], fs.Args()[1:]...) - f, err := pty.Start(c) - if err != nil { - fmt.Printf("pty.Start failed: %v", err) - return subcommands.ExitFailure - } - defer f.Close() - - // Copy stdout from the command to keep this process alive until the - // subprocess exits. - io.Copy(os.Stdout, f) - - return subcommands.ExitSuccess -} diff --git a/test/e2e/BUILD b/test/e2e/BUILD deleted file mode 100644 index 29a84f184..000000000 --- a/test/e2e/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_test( - name = "integration_test", - size = "large", - srcs = [ - "exec_test.go", - "integration_test.go", - "regression_test.go", - ], - library = ":integration", - tags = [ - # Requires docker and runsc to be configured before the test runs. - "manual", - "local", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/abi/linux", - "//pkg/bits", - "//pkg/test/dockerutil", - "//pkg/test/testutil", - "//runsc/specutils", - "@com_github_docker_docker//api/types/mount:go_default_library", - ], -) - -go_library( - name = "integration", - srcs = ["integration.go"], -) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go deleted file mode 100644 index b47df447c..000000000 --- a/test/e2e/exec_test.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package integration provides end-to-end integration tests for runsc. These -// tests require docker and runsc to be installed on the machine. -// -// Each test calls docker commands to start up a container, and tests that it -// is behaving properly, with various runsc commands. The container is killed -// and deleted at the end. - -package integration - -import ( - "context" - "fmt" - "strconv" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/bits" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/runsc/specutils" -) - -// Test that exec uses the exact same capability set as the container. -func TestExecCapabilities(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Check that capability. - matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second) - if err != nil { - t.Fatalf("WaitForOutputSubmatch() timeout: %v", err) - } - if len(matches) != 2 { - t.Fatalf("There should be a match for the whole line and the capability bitmask") - } - want := fmt.Sprintf("CapEff:\t%s\n", matches[1]) - t.Log("Root capabilities:", want) - - // Now check that exec'd process capabilities match the root. - got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "grep", "CapEff:", "/proc/self/status") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - t.Logf("CapEff: %v", got) - if got != want { - t.Errorf("wrong capabilities, got: %q, want: %q", got, want) - } -} - -// Test that 'exec --privileged' adds all capabilities, except for CAP_NET_RAW -// which is removed from the container when --net-raw=false. -func TestExecPrivileged(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container with all capabilities dropped. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - CapDrop: []string{"all"}, - }, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Check that all capabilities where dropped from container. - matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second) - if err != nil { - t.Fatalf("WaitForOutputSubmatch() timeout: %v", err) - } - if len(matches) != 2 { - t.Fatalf("There should be a match for the whole line and the capability bitmask") - } - containerCaps, err := strconv.ParseUint(matches[1], 16, 64) - if err != nil { - t.Fatalf("failed to convert capabilities %q: %v", matches[1], err) - } - t.Logf("Container capabilities: %#x", containerCaps) - if containerCaps != 0 { - t.Fatalf("Container should have no capabilities: %x", containerCaps) - } - - // Check that 'exec --privileged' adds all capabilities, except for - // CAP_NET_RAW. - got, err := d.Exec(ctx, dockerutil.ExecOpts{ - Privileged: true, - }, "grep", "CapEff:", "/proc/self/status") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - t.Logf("Exec CapEff: %v", got) - want := fmt.Sprintf("CapEff:\t%016x\n", specutils.AllCapabilitiesUint64()&^bits.MaskOf64(int(linux.CAP_NET_RAW))) - if got != want { - t.Errorf("Wrong capabilities, got: %q, want: %q. Make sure runsc is not using '--net-raw'", got, want) - } -} - -func TestExecJobControl(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - p, err := d.ExecProcess(ctx, dockerutil.ExecOpts{UseTTY: true}, "/bin/sh") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - - if _, err = p.Write(time.Second, []byte("sleep 100 | cat\n")); err != nil { - t.Fatalf("error exit: %v", err) - } - time.Sleep(time.Second) - - if _, err = p.Write(time.Second, []byte{0x03}); err != nil { - t.Fatalf("error exit: %v", err) - } - - if _, err = p.Write(time.Second, []byte("exit $(expr $? + 10)\n")); err != nil { - t.Fatalf("error exit: %v", err) - } - - want := 140 - got, err := p.WaitExitStatus(ctx) - if err != nil { - t.Fatalf("wait for exit failed with: %v", err) - } else if got != want { - t.Fatalf("wait for exit returned: %d want: %d", got, want) - } -} - -// Test that failure to exec returns proper error message. -func TestExecError(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Attempt to exec a binary that doesn't exist. - out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "no_can_find") - if err == nil { - t.Fatalf("docker exec didn't fail") - } - if want := `error finding executable "no_can_find" in PATH`; !strings.Contains(out, want) { - t.Fatalf("docker exec wrong error, got: %s, want: .*%s.*", out, want) - } -} - -// Test that exec inherits environment from run. -func TestExecEnv(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container with env FOO=BAR. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - Env: []string{"FOO=BAR"}, - }, "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Exec "echo $FOO". - got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $FOO") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - if got, want := strings.TrimSpace(got), "BAR"; got != want { - t.Errorf("bad output from 'docker exec'. Got %q; Want %q.", got, want) - } -} - -// TestRunEnvHasHome tests that run always has HOME environment set. -func TestRunEnvHasHome(t *testing.T) { - // Base alpine image does not have any environment variables set. - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Exec "echo $HOME". The 'bin' user's home dir is '/bin'. - got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - User: "bin", - }, "/bin/sh", "-c", "echo $HOME") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Check that the directory matches. - if got, want := strings.TrimSpace(got), "/bin"; got != want { - t.Errorf("bad output from 'docker run'. Got %q; Want %q.", got, want) - } -} - -// Test that exec always has HOME environment set, even when not set in run. -func TestExecEnvHasHome(t *testing.T) { - // Base alpine image does not have any environment variables set. - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "1000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Exec "echo $HOME", and expect to see "/root". - got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $HOME") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - if want := "/root"; !strings.Contains(got, want) { - t.Errorf("wanted exec output to contain %q, got %q", want, got) - } - - // Create a new user with a home directory. - newUID := 1234 - newHome := "/foo/bar" - cmd := fmt.Sprintf("mkdir -p -m 777 %q && adduser foo -D -u %d -h %q", newHome, newUID, newHome) - if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", cmd); err != nil { - t.Fatalf("docker exec failed: %v", err) - } - - // Execute the same as the new user and expect newHome. - got, err = d.Exec(ctx, dockerutil.ExecOpts{ - User: strconv.Itoa(newUID), - }, "/bin/sh", "-c", "echo $HOME") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - if want := newHome; !strings.Contains(got, want) { - t.Errorf("wanted exec output to contain %q, got %q", want, got) - } -} diff --git a/test/e2e/integration.go b/test/e2e/integration.go deleted file mode 100644 index 4cd5f6c24..000000000 --- a/test/e2e/integration.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package integration is empty. See integration_test.go for description. -package integration diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go deleted file mode 100644 index 49cd74887..000000000 --- a/test/e2e/integration_test.go +++ /dev/null @@ -1,557 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package integration provides end-to-end integration tests for runsc. -// -// Each test calls docker commands to start up a container, and tests that it is -// behaving properly, with various runsc commands. The container is killed and -// deleted at the end. -// -// Setup instruction in test/README.md. -package integration - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "net" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/docker/docker/api/types/mount" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// defaultWait is the default wait time used for tests. -const defaultWait = time.Minute - -// httpRequestSucceeds sends a request to a given url and checks that the status is OK. -func httpRequestSucceeds(client http.Client, server string, port int) error { - url := fmt.Sprintf("http://%s:%d", server, port) - // Ensure that content is being served. - resp, err := client.Get(url) - if err != nil { - return fmt.Errorf("error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - return nil -} - -// TestLifeCycle tests a basic Create/Start/Stop docker container life cycle. -func TestLifeCycle(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 80 - if err := d.Create(ctx, dockerutil.RunOpts{ - Image: "basic/nginx", - Ports: []int{port}, - }); err != nil { - t.Fatalf("docker create failed: %v", err) - } - if err := d.Start(ctx); err != nil { - t.Fatalf("docker start failed: %v", err) - } - - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { - t.Errorf("http request failed: %v", err) - } - - if err := d.Stop(ctx); err != nil { - t.Fatalf("docker stop failed: %v", err) - } - if err := d.Remove(ctx); err != nil { - t.Fatalf("docker rm failed: %v", err) - } -} - -func TestPauseResume(t *testing.T) { - if !testutil.IsCheckpointSupported() { - t.Skip("Checkpoint is not supported.") - } - - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 8080 - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/python", - Ports: []int{port}, // See Dockerfile. - }); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Check that container is working. - client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { - t.Error("http request failed:", err) - } - - if err := d.Pause(ctx); err != nil { - t.Fatalf("docker pause failed: %v", err) - } - - // Check if container is paused. - client = http.Client{Timeout: 10 * time.Millisecond} // Don't wait a minute. - switch _, err := client.Get(fmt.Sprintf("http://%s:%d", ip.String(), port)); v := err.(type) { - case nil: - t.Errorf("http req expected to fail but it succeeded") - case net.Error: - if !v.Timeout() { - t.Errorf("http req got error %v, wanted timeout", v) - } - default: - t.Errorf("http req got unexpected error %v", v) - } - - if err := d.Unpause(ctx); err != nil { - t.Fatalf("docker unpause failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Check if container is working again. - client = http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { - t.Error("http request failed:", err) - } -} - -func TestCheckpointRestore(t *testing.T) { - if !testutil.IsCheckpointSupported() { - t.Skip("Pause/resume is not supported.") - } - - // TODO(gvisor.dev/issue/3373): Remove after implementing. - if usingVFS2, err := dockerutil.UsingVFS2(); usingVFS2 { - t.Skip("CheckpointRestore not implemented in VFS2.") - } else if err != nil { - t.Fatalf("failed to read config for runtime %s: %v", dockerutil.Runtime(), err) - } - - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 8080 - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/python", - Ports: []int{port}, // See Dockerfile. - }); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Create a snapshot. - if err := d.Checkpoint(ctx, "test"); err != nil { - t.Fatalf("docker checkpoint failed: %v", err) - } - if err := d.WaitTimeout(ctx, defaultWait); err != nil { - t.Fatalf("wait failed: %v", err) - } - - // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. - if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { - t.Fatalf("docker restore failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Check if container is working again. - client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { - t.Error("http request failed:", err) - } -} - -// Create client and server that talk to each other using the local IP. -func TestConnectToSelf(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Creates server that replies "server" and exists. Sleeps at the end because - // 'docker exec' gets killed if the init process exists before it can finish. - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/ubuntu", - }, "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Finds IP address for host. - ip, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'") - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - ip = strings.TrimRight(ip, "\n") - - // Runs client that sends "client" to the server and exits. - reply, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip)) - if err != nil { - t.Fatalf("docker exec failed: %v", err) - } - - // Ensure both client and server got the message from each other. - if want := "server\n"; reply != want { - t.Errorf("Error on server, want: %q, got: %q", want, reply) - } - if _, err := d.WaitForOutput(ctx, "^client\n$", defaultWait); err != nil { - t.Fatalf("docker.WaitForOutput(client) timeout: %v", err) - } -} - -func TestMemLimit(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - allocMemoryKb := 50 * 1024 - out, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - 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) - } - - // Remove warning message that swap isn't present. - if strings.HasPrefix(out, "WARNING") { - lines := strings.Split(out, "\n") - if len(lines) != 3 { - t.Fatalf("invalid output: %s", out) - } - out = lines[1] - } - - // Ensure the memory matches what we want. - got, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) - if err != nil { - t.Fatalf("failed to parse %q: %v", out, err) - } - if want := uint64(allocMemoryKb); got != want { - t.Errorf("MemTotal got: %d, want: %d", got, want) - } -} - -func TestNumCPU(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Read how many cores are in the container. - out, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - CpusetCpus: "0", - }, "sh", "-c", "cat /proc/cpuinfo | grep 'processor.*:' | wc -l") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Ensure it matches what we want. - got, err := strconv.Atoi(strings.TrimSpace(out)) - if err != nil { - t.Fatalf("failed to parse %q: %v", out, err) - } - if want := 1; got != want { - t.Errorf("MemTotal got: %d, want: %d", got, want) - } -} - -// TestJobControl tests that job control characters are handled properly. -func TestJobControl(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container with an attached PTY. - p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sh", "-c", "sleep 100 | cat") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - // Give shell a few seconds to start executing the sleep. - time.Sleep(2 * time.Second) - - if _, err := p.Write(time.Second, []byte{0x03}); err != nil { - t.Fatalf("error exit: %v", err) - } - - if err := d.WaitTimeout(ctx, 3*time.Second); err != nil { - t.Fatalf("WaitTimeout failed: %v", err) - } - - want := 130 - got, err := p.WaitExitStatus(ctx) - if err != nil { - t.Fatalf("wait for exit failed with: %v", err) - } else if got != want { - t.Fatalf("got: %d want: %d", got, want) - } -} - -// TestWorkingDirCreation checks that working dir is created if it doesn't exit. -func TestWorkingDirCreation(t *testing.T) { - for _, tc := range []struct { - name string - workingDir string - }{ - {name: "root", workingDir: "/foo"}, - {name: "tmp", workingDir: "/tmp/foo"}, - } { - for _, readonly := range []bool{true, false} { - name := tc.name - if readonly { - name += "-readonly" - } - t.Run(name, func(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - opts := dockerutil.RunOpts{ - Image: "basic/alpine", - WorkDir: tc.workingDir, - ReadOnly: readonly, - } - got, err := d.Run(ctx, opts, "sh", "-c", "echo ${PWD}") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - if want := tc.workingDir + "\n"; want != got { - t.Errorf("invalid working dir, want: %q, got: %q", want, got) - } - }) - } - } -} - -// TestTmpFile checks that files inside '/tmp' are not overridden. -func TestTmpFile(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - opts := dockerutil.RunOpts{Image: "basic/tmpfile"} - got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - if want := "123\n"; want != got { - t.Errorf("invalid file content, want: %q, got: %q", want, got) - } -} - -// TestTmpMount checks that mounts inside '/tmp' are not overridden. -func TestTmpMount(t *testing.T) { - ctx := context.Background() - dir, err := ioutil.TempDir(testutil.TmpDir(), "tmp-mount") - if err != nil { - t.Fatalf("TempDir(): %v", err) - } - want := "123" - if err := ioutil.WriteFile(filepath.Join(dir, "file.txt"), []byte("123"), 0666); err != nil { - t.Fatalf("WriteFile(): %v", err) - } - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - opts := dockerutil.RunOpts{ - Image: "basic/alpine", - Mounts: []mount.Mount{ - { - Type: mount.TypeBind, - Source: dir, - Target: "/tmp/foo", - }, - }, - } - got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - if want != got { - t.Errorf("invalid file content, want: %q, got: %q", want, got) - } -} - -// TestSyntheticDirs checks that submounts can be created inside a readonly -// mount even if the target path does not exist. -func TestSyntheticDirs(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - opts := dockerutil.RunOpts{ - Image: "basic/alpine", - // Make the root read-only to force use of synthetic dirs - // inside the root gofer mount. - ReadOnly: true, - Mounts: []mount.Mount{ - // Mount inside read-only gofer-backed root. - { - Type: mount.TypeTmpfs, - Target: "/foo/bar/baz", - }, - // Mount inside sysfs, which always uses synthetic dirs - // for submounts. - { - Type: mount.TypeTmpfs, - Target: "/sys/foo/bar/baz", - }, - }, - } - // Make sure the directories exist. - if _, err := d.Run(ctx, opts, "ls", "/foo/bar/baz", "/sys/foo/bar/baz"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - -} - -// TestHostOverlayfsCopyUp tests that the --overlayfs-stale-read option causes -// runsc to hide the incoherence of FDs opened before and after overlayfs -// copy-up on the host. -func TestHostOverlayfsCopyUp(t *testing.T) { - runIntegrationTest(t, nil, "./test_copy_up") -} - -// TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory -// stream to refer to the current state of the corresponding directory, as a -// call to opendir() would have done" as required by POSIX, when the directory -// in question is host overlayfs. -// -// This test specifically targets host overlayfs because, per POSIX, "if a file -// is removed from or added to the directory after the most recent call to -// opendir() or rewinddir(), whether a subsequent call to readdir() returns an -// entry for that file is unspecified"; the host filesystems used by other -// automated tests yield newly-added files from readdir() even if the fsgofer -// does not explicitly rewinddir(), but overlayfs does not. -func TestHostOverlayfsRewindDir(t *testing.T) { - runIntegrationTest(t, nil, "./test_rewinddir") -} - -// Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it -// cannot use tricks like userns as root. For this reason, run a basic link test -// to ensure some coverage. -func TestLink(t *testing.T) { - runIntegrationTest(t, nil, "./link_test") -} - -// This test ensures we can run ping without errors. -func TestPing4Loopback(t *testing.T) { - if testutil.IsRunningWithHostNet() { - // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable - // this test. - t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.") - } - - runIntegrationTest(t, nil, "./ping4.sh") -} - -// This test ensures we can enable ipv6 on loopback and run ping6 without -// errors. -func TestPing6Loopback(t *testing.T) { - if testutil.IsRunningWithHostNet() { - // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable - // this test. - t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.") - } - - // The CAP_NET_ADMIN capability is required to use the `ip` utility, which - // we use to enable ipv6 on loopback. - // - // By default, ipv6 loopback is not enabled by runsc, because docker does - // not assign an ipv6 address to the test container. - runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh") -} - -// This test checks that the owner of the sticky directory can delete files -// inside it belonging to other users. It also checks that the owner of a file -// can always delete its file when the file is inside a sticky directory owned -// by another user. -func TestStickyDir(t *testing.T) { - if vfs2Used, err := dockerutil.UsingVFS2(); err != nil { - t.Fatalf("failed to read config for runtime %s: %v", dockerutil.Runtime(), err) - } else if !vfs2Used { - t.Skip("sticky bit test fails on VFS1.") - } - - runIntegrationTest(t, nil, "./test_sticky") -} - -func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/integrationtest", - WorkDir: "/root", - CapAdd: capAdd, - }, args...); err != nil { - t.Fatalf("docker run failed: %v", err) - } else if got != "" { - t.Errorf("test failed:\n%s", got) - } -} - -func TestMain(m *testing.M) { - dockerutil.EnsureSupportedDockerVersion() - flag.Parse() - os.Exit(m.Run()) -} diff --git a/test/e2e/regression_test.go b/test/e2e/regression_test.go deleted file mode 100644 index 84564cdaa..000000000 --- a/test/e2e/regression_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package integration - -import ( - "context" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" -) - -// Test that UDS can be created using overlay when parent directory is in lower -// layer only (b/134090485). -// -// Prerequisite: the directory where the socket file is created must not have -// been open for write before bind(2) is called. -func TestBindOverlay(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Run the container. - got, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/ubuntu", - }, "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) - } - - // Check the output contains what we want. - if want := "foobar-asdf"; !strings.Contains(got, want) { - t.Fatalf("docker run output is missing %q: %s", want, got) - } -} diff --git a/test/fsstress/BUILD b/test/fsstress/BUILD deleted file mode 100644 index d262c8554..000000000 --- a/test/fsstress/BUILD +++ /dev/null @@ -1,25 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_test( - name = "fsstress_test", - size = "large", - srcs = [ - "fsstress_test.go", - ], - library = ":fsstress", - tags = [ - # Requires docker and runsc to be configured before the test runs. - "manual", - "local", - ], - deps = [ - "//pkg/test/dockerutil", - ], -) - -go_library( - name = "fsstress", - srcs = ["fsstress.go"], -) diff --git a/test/fsstress/fsstress.go b/test/fsstress/fsstress.go deleted file mode 100644 index 464237e2d..000000000 --- a/test/fsstress/fsstress.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package fsstress is empty. See fsstress_test.go for description. -package fsstress diff --git a/test/fsstress/fsstress_test.go b/test/fsstress/fsstress_test.go deleted file mode 100644 index 300c21ceb..000000000 --- a/test/fsstress/fsstress_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package fsstress runs fsstress tool inside a docker container. -package fsstress - -import ( - "context" - "math/rand" - "strconv" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" -) - -func init() { - rand.Seed(int64(time.Now().Nanosecond())) -} - -func fsstress(t *testing.T, dir string) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - const ( - operations = "10000" - processes = "100" - image = "basic/fsstress" - ) - seed := strconv.FormatUint(uint64(rand.Uint32()), 10) - args := []string{"-d", dir, "-n", operations, "-p", processes, "-s", seed, "-X"} - t.Logf("Repro: docker run --rm --runtime=runsc %s %s", image, strings.Join(args, "")) - out, err := d.Run(ctx, dockerutil.RunOpts{Image: image}, args...) - if err != nil { - t.Fatalf("docker run failed: %v\noutput: %s", err, out) - } - lines := strings.SplitN(out, "\n", 2) - if len(lines) > 1 || !strings.HasPrefix(out, "seed =") { - t.Fatalf("unexpected output: %s", out) - } -} - -func TestFsstressGofer(t *testing.T) { - fsstress(t, "/test") -} - -func TestFsstressTmpfs(t *testing.T) { - fsstress(t, "/tmp") -} diff --git a/test/fuse/BUILD b/test/fuse/BUILD deleted file mode 100644 index 74500ec84..000000000 --- a/test/fuse/BUILD +++ /dev/null @@ -1,78 +0,0 @@ -load("//test/runner:defs.bzl", "syscall_test") - -package(licenses = ["notice"]) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:stat_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:open_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:release_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:mknod_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:symlink_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:readlink_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:mkdir_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:read_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:write_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:rmdir_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:readdir_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:create_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:unlink_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:setstat_test", -) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:mount_test", -) diff --git a/test/fuse/README.md b/test/fuse/README.md deleted file mode 100644 index 65add57e2..000000000 --- a/test/fuse/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# gVisor FUSE Test Suite - -This is an integration test suite for fuse(4) filesystem. It runs under gVisor -sandbox container with VFS2 and FUSE function enabled. - -This document describes the framework of FUSE integration test, how to use it, -and the guidelines that should be followed when adding new testing features. - -## Integration Test Framework - -By inheriting the `FuseTest` class defined in `linux/fuse_base.h`, every test -fixture can run in an environment with `mount_point_` mounted by a fake FUSE -server. It creates a `socketpair(2)` to send and receive control commands and -data between the client and the server. Because the FUSE server runs in the -background thread, gTest cannot catch its assertion failure immediately. Thus, -`TearDown()` function sends command to the FUSE server to check if all gTest -assertion in the server are successful and all requests and preset responses are -consumed. - -## Communication Diagram - -Diagram below describes how a testing thread communicates with the FUSE server -to achieve integration test. - -For the following diagram, `>` means entering the function, `<` is leaving the -function, and `=` indicates sequentially entering and leaving. Not necessarily -follow exactly the below diagram due to the nature of a multi-threaded system, -however, it is still helpful to know when the client waits for the server to -complete a command and when the server awaits the next instruction. - -``` -| Client (Testing Thread) | Server (FUSE Server Thread) -| | -| >TEST_F() | -| >SetUp() | -| =MountFuse() | -| >SetUpFuseServer() | -| [create communication socket]| -| =fork() | =fork() -| [wait server complete] | -| | =ServerConsumeFuseInit() -| | =ServerCompleteWith() -| <SetUpFuseServer() | -| <SetUp() | -| [testing main] | -| | >ServerFuseLoop() -| | [poll on socket and fd] -| >SetServerResponse() | -| [write data to socket] | -| [wait server complete] | -| | [socket event occurs] -| | >ServerHandleCommand() -| | >ServerReceiveResponse() -| | [read data from socket] -| | [save data to memory] -| | <ServerReceiveResponse() -| | =ServerCompleteWith() -| <SetServerResponse() | -| | <ServerHandleCommand() -| >[Do fs operation] | -| [wait for fs response] | -| | [fd event occurs] -| | >ServerProcessFuseRequest() -| | =[read fs request] -| | =[save fs request to memory] -| | =[write fs response] -| <[Do fs operation] | -| | <ServerProcessFuseRequest() -| | -| =[Test fs operation result] | -| | -| >GetServerActualRequest() | -| [write data to socket] | -| [wait data from server] | -| | [socket event occurs] -| | >ServerHandleCommand() -| | >ServerSendReceivedRequest() -| | [write data to socket] -| [read data from socket] | -| [wait server complete] | -| | <ServerSendReceivedRequest() -| | =ServerCompleteWith() -| <GetServerActualRequest() | -| | <ServerHandleCommand() -| | -| =[Test actual request] | -| | -| >TearDown() | -| ... | -| >GetServerNumUnsentResponses() | -| [write data to socket] | -| [wait server complete] | -| | [socket event arrive] -| | >ServerHandleCommand() -| | >ServerSendData() -| | [write data to socket] -| | <ServerSendData() -| | =ServerCompleteWith() -| [read data from socket] | -| [test if all succeeded] | -| <GetServerNumUnsentResponses() | -| | <ServerHandleCommand() -| =UnmountFuse() | -| <TearDown() | -| <TEST_F() | -``` - -## Running the tests - -Based on syscall tests, FUSE tests generate targets only with vfs2 and fuse -enabled. The corresponding targets end in `_fuse`. - -For example, to run fuse test in `stat_test.cc`: - -```bash -$ bazel test //test/fuse:stat_test_runsc_ptrace_vfs2_fuse -``` - -Test all targets tagged with fuse: - -```bash -$ bazel test --test_tag_filters=fuse //test/fuse/... -``` - -## Writing a new FUSE test - -1. Add test targets in `BUILD` and `linux/BUILD`. -2. Inherit your test from `FuseTest` base class. It allows you to: - - Fork a fake FUSE server in background during each test setup. - - Create a pair of sockets for communication and provide utility - functions. - - Stop FUSE server and check if error occurs in it after test completes. -3. Build the expected opcode-response pairs of your FUSE operation. -4. Call `SetServerResponse()` to preset the next expected opcode and response. -5. Do real filesystem operations (FUSE is mounted at `mount_point_`). -6. Check FUSE response and/or errors. -7. Retrieve FUSE request by `GetServerActualRequest()`. -8. Check if the request is as expected. - -A few customized matchers used in syscalls test are encouraged to test the -outcome of filesystem operations. Such as: - -```cc -SyscallSucceeds() -SyscallSucceedsWithValue(...) -SyscallFails() -SyscallFailsWithErrno(...) -``` - -Please refer to [test/syscalls/README.md](../syscalls/README.md) for further -details. - -## Writing a new FuseTestCmd - -A `FuseTestCmd` is a control protocol used in the communication between the -testing thread and the FUSE server. Such commands are sent from the testing -thread to the FUSE server to set up, control, or inspect the behavior of the -FUSE server in response to a sequence of FUSE requests. - -The lifecycle of a command contains following steps: - -1. The testing thread sends a `FuseTestCmd` via socket and waits for - completion. -2. The FUSE server receives the command and does corresponding action. -3. (Optional) The testing thread reads data from socket. -4. The FUSE server sends a success indicator via socket after processing. -5. The testing thread gets the success signal and continues testing. - -The success indicator, i.e. `WaitServerComplete()`, is crucial at the end of -each `FuseTestCmd` sent from the testing thread. Because we don't want to begin -filesystem operation if the requests have not been completely set up. Also, to -test FUSE interactions in a sequential manner, concurrent requests are not -supported now. - -To add a new `FuseTestCmd`, one must comply with following format: - -1. Add a new `FuseTestCmd` enum class item defined in `linux/fuse_base.h` -2. Add a `SetServerXXX()` or `GetServerXXX()` public function in `FuseTest`. - This is how the testing thread will call to send control message. Define how - many bytes you want to send along with the command and what you will expect - to receive. Finally it should block and wait for a success indicator from - the FUSE server. -3. Add a handler logic in the switch condition of `ServerHandleCommand()`. Use - `ServerSendData()` or declare a new private function such as - `ServerReceiveXXX()` or `ServerSendXXX()`. It is mandatory to set it private - since only the FUSE server (forked from `FuseTest` base class) can call it. - This is the server part of the specific `FuseTestCmd` and the format of the - data should be consistent with what the client expects in the previous step. diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD deleted file mode 100644 index 2f745bd47..000000000 --- a/test/fuse/linux/BUILD +++ /dev/null @@ -1,243 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "cc_library", "gtest") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -cc_binary( - name = "stat_test", - testonly = 1, - srcs = ["stat_test.cc"], - deps = [ - gtest, - ":fuse_fd_util", - "//test/util:cleanup", - "//test/util:fs_util", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "open_test", - testonly = 1, - srcs = ["open_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "release_test", - testonly = 1, - srcs = ["release_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mknod_test", - testonly = 1, - srcs = ["mknod_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "symlink_test", - testonly = 1, - srcs = ["symlink_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "readlink_test", - testonly = 1, - srcs = ["readlink_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mkdir_test", - testonly = 1, - srcs = ["mkdir_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "setstat_test", - testonly = 1, - srcs = ["setstat_test.cc"], - deps = [ - gtest, - ":fuse_fd_util", - "//test/util:cleanup", - "//test/util:fs_util", - "//test/util:fuse_util", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "rmdir_test", - testonly = 1, - srcs = ["rmdir_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fs_util", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "readdir_test", - testonly = 1, - srcs = ["readdir_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fs_util", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_library( - name = "fuse_base", - testonly = 1, - srcs = ["fuse_base.cc"], - hdrs = ["fuse_base.h"], - deps = [ - gtest, - "//test/util:fuse_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_util", - "@com_google_absl//absl/strings:str_format", - ], -) - -cc_library( - name = "fuse_fd_util", - testonly = 1, - srcs = ["fuse_fd_util.cc"], - hdrs = ["fuse_fd_util.h"], - deps = [ - gtest, - ":fuse_base", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fuse_util", - "//test/util:posix_error", - ], -) - -cc_binary( - name = "read_test", - testonly = 1, - srcs = ["read_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "write_test", - testonly = 1, - srcs = ["write_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "create_test", - testonly = 1, - srcs = ["create_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fs_util", - "//test/util:fuse_util", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "unlink_test", - testonly = 1, - srcs = ["unlink_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:fuse_util", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mount_test", - testonly = 1, - 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/create_test.cc b/test/fuse/linux/create_test.cc deleted file mode 100644 index 9a0219a58..000000000 --- a/test/fuse/linux/create_test.cc +++ /dev/null @@ -1,128 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fs_util.h" -#include "test/util/fuse_util.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class CreateTest : public FuseTest { - protected: - const std::string test_file_name_ = "test_file"; - const mode_t mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(CreateTest, CreateFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_name_); - - // Ensure the file doesn't exist. - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - .error = -ENOENT, - }; - auto iov_out = FuseGenerateIovecs(out_header); - SetServerResponse(FUSE_LOOKUP, iov_out); - - // creat(2) is equal to open(2) with open_flags O_CREAT | O_WRONLY | O_TRUNC. - const mode_t new_mask = S_IWGRP | S_IWOTH; - const int open_flags = O_CREAT | O_WRONLY | O_TRUNC; - out_header.error = 0; - out_header.len = sizeof(struct fuse_out_header) + - sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out); - struct fuse_entry_out entry_payload = DefaultEntryOut(mode & ~new_mask, 2); - struct fuse_open_out out_payload = { - .fh = 1, - .open_flags = open_flags, - }; - iov_out = FuseGenerateIovecs(out_header, entry_payload, out_payload); - SetServerResponse(FUSE_CREATE, iov_out); - - // kernfs generates a successive FUSE_OPEN after the file is created. Linux's - // fuse kernel module will not send this FUSE_OPEN after creat(2). - out_header.len = - sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out); - iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_OPEN, iov_out); - - int fd; - TempUmask mask(new_mask); - EXPECT_THAT(fd = creat(test_file_path.c_str(), mode), SyscallSucceeds()); - EXPECT_THAT(fcntl(fd, F_GETFL), - SyscallSucceedsWithValue(open_flags & O_ACCMODE)); - - struct fuse_in_header in_header; - struct fuse_create_in in_payload; - std::vector<char> name(test_file_name_.size() + 1); - auto iov_in = FuseGenerateIovecs(in_header, in_payload, name); - - // Skip the request of FUSE_LOOKUP. - SkipServerActualRequest(); - - // Get the first FUSE_CREATE. - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload) + - test_file_name_.size() + 1); - EXPECT_EQ(in_header.opcode, FUSE_CREATE); - EXPECT_EQ(in_payload.flags, open_flags); - EXPECT_EQ(in_payload.mode, mode & ~new_mask); - EXPECT_EQ(in_payload.umask, new_mask); - EXPECT_EQ(std::string(name.data()), test_file_name_); - - // Get the successive FUSE_OPEN. - struct fuse_open_in in_payload_open; - iov_in = FuseGenerateIovecs(in_header, in_payload_open); - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload_open)); - EXPECT_EQ(in_header.opcode, FUSE_OPEN); - EXPECT_EQ(in_payload_open.flags, open_flags & O_ACCMODE); - - EXPECT_THAT(close(fd), SyscallSucceeds()); - // Skip the FUSE_RELEASE. - SkipServerActualRequest(); -} - -TEST_F(CreateTest, CreateFileAlreadyExists) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_name_); - - const int open_flags = O_CREAT | O_EXCL; - - SetServerInodeLookup(test_file_name_); - - EXPECT_THAT(open(test_file_path.c_str(), mode, open_flags), - SyscallFailsWithErrno(EEXIST)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc deleted file mode 100644 index 5b45804e1..000000000 --- a/test/fuse/linux/fuse_base.cc +++ /dev/null @@ -1,447 +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. - -#include "test/fuse/linux/fuse_base.h" - -#include <fcntl.h> -#include <linux/fuse.h> -#include <poll.h> -#include <sys/mount.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "test/util/fuse_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -void FuseTest::SetUp() { - MountFuse(); - SetUpFuseServer(); -} - -void FuseTest::TearDown() { - EXPECT_EQ(GetServerNumUnconsumedRequests(), 0); - EXPECT_EQ(GetServerNumUnsentResponses(), 0); - UnmountFuse(); -} - -// Sends 3 parts of data to the FUSE server: -// 1. The `kSetResponse` command -// 2. The expected opcode -// 3. The fake FUSE response -// Then waits for the FUSE server to notify its completion. -void FuseTest::SetServerResponse(uint32_t opcode, - std::vector<struct iovec>& iovecs) { - uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSetResponse); - EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - EXPECT_THAT(RetryEINTR(write)(sock_[0], &opcode, sizeof(opcode)), - SyscallSucceedsWithValue(sizeof(opcode))); - - EXPECT_THAT(RetryEINTR(writev)(sock_[0], iovecs.data(), iovecs.size()), - SyscallSucceeds()); - - WaitServerComplete(); -} - -// Waits for the FUSE server to finish its blocking job and check if it -// completes without errors. -void FuseTest::WaitServerComplete() { - uint32_t success; - EXPECT_THAT(RetryEINTR(read)(sock_[0], &success, sizeof(success)), - SyscallSucceedsWithValue(sizeof(success))); - ASSERT_EQ(success, 1); -} - -// Sends the `kGetRequest` command to the FUSE server, then reads the next -// request into iovec struct. The order of calling this function should be -// the same as the one of SetServerResponse(). -void FuseTest::GetServerActualRequest(std::vector<struct iovec>& iovecs) { - uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kGetRequest); - EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - EXPECT_THAT(RetryEINTR(readv)(sock_[0], iovecs.data(), iovecs.size()), - SyscallSucceeds()); - - WaitServerComplete(); -} - -// Sends a FuseTestCmd command to the FUSE server, reads from the socket, and -// returns the corresponding data. -uint32_t FuseTest::GetServerData(uint32_t cmd) { - uint32_t data; - EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - EXPECT_THAT(RetryEINTR(read)(sock_[0], &data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); - - WaitServerComplete(); - return data; -} - -uint32_t FuseTest::GetServerNumUnconsumedRequests() { - return GetServerData( - static_cast<uint32_t>(FuseTestCmd::kGetNumUnconsumedRequests)); -} - -uint32_t FuseTest::GetServerNumUnsentResponses() { - return GetServerData( - static_cast<uint32_t>(FuseTestCmd::kGetNumUnsentResponses)); -} - -uint32_t FuseTest::GetServerTotalReceivedBytes() { - return GetServerData( - static_cast<uint32_t>(FuseTestCmd::kGetTotalReceivedBytes)); -} - -// Sends the `kSkipRequest` command to the FUSE server, which would skip -// current stored request data. -void FuseTest::SkipServerActualRequest() { - uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSkipRequest); - EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - WaitServerComplete(); -} - -// Sends the `kSetInodeLookup` command, expected mode, and the path of the -// inode to create under the mount point. -void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode, - uint64_t size) { - uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSetInodeLookup); - EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - EXPECT_THAT(RetryEINTR(write)(sock_[0], &mode, sizeof(mode)), - SyscallSucceedsWithValue(sizeof(mode))); - - EXPECT_THAT(RetryEINTR(write)(sock_[0], &size, sizeof(size)), - SyscallSucceedsWithValue(sizeof(size))); - - // Pad 1 byte for null-terminate c-string. - EXPECT_THAT(RetryEINTR(write)(sock_[0], path.c_str(), path.size() + 1), - SyscallSucceedsWithValue(path.size() + 1)); - - WaitServerComplete(); -} - -void FuseTest::MountFuse(const char* mountOpts) { - EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds()); - - std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, mountOpts); - mount_point_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(mount("fuse", mount_point_.path().c_str(), "fuse", - MS_NODEV | MS_NOSUID, mount_opts.c_str()), - SyscallSucceeds()); -} - -void FuseTest::UnmountFuse() { - EXPECT_THAT(umount(mount_point_.path().c_str()), SyscallSucceeds()); - // TODO(gvisor.dev/issue/3330): ensure the process is terminated successfully. -} - -// Consumes the first FUSE request and returns the corresponding PosixError. -PosixError FuseTest::ServerConsumeFuseInit( - const struct fuse_init_out* out_payload) { - std::vector<char> buf(FUSE_MIN_READ_BUFFER); - RETURN_ERROR_IF_SYSCALL_FAIL( - RetryEINTR(read)(dev_fd_, buf.data(), buf.size())); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out), - .error = 0, - .unique = 2, - }; - // Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED - // error in the initialization of FUSE connection. - auto iov_out = FuseGenerateIovecs( - out_header, *const_cast<struct fuse_init_out*>(out_payload)); - - RETURN_ERROR_IF_SYSCALL_FAIL( - RetryEINTR(writev)(dev_fd_, iov_out.data(), iov_out.size())); - return NoError(); -} - -// Reads 1 expected opcode and a fake response from socket and save them into -// the serial buffer of this testing instance. -void FuseTest::ServerReceiveResponse() { - ssize_t len; - uint32_t opcode; - std::vector<char> buf(FUSE_MIN_READ_BUFFER); - EXPECT_THAT(RetryEINTR(read)(sock_[1], &opcode, sizeof(opcode)), - SyscallSucceedsWithValue(sizeof(opcode))); - - EXPECT_THAT(len = RetryEINTR(read)(sock_[1], buf.data(), buf.size()), - SyscallSucceeds()); - - responses_.AddMemBlock(opcode, buf.data(), len); -} - -// Writes 1 byte of success indicator through socket. -void FuseTest::ServerCompleteWith(bool success) { - uint32_t data = success ? 1 : 0; - ServerSendData(data); -} - -// ServerFuseLoop is the implementation of the fake FUSE server. Monitors 2 -// file descriptors: /dev/fuse and sock_[1]. Events from /dev/fuse are FUSE -// requests and events from sock_[1] are FUSE testing commands, leading by -// a FuseTestCmd data to indicate the command. -void FuseTest::ServerFuseLoop() { - const int nfds = 2; - struct pollfd fds[nfds] = { - { - .fd = dev_fd_, - .events = POLL_IN | POLLHUP | POLLERR | POLLNVAL, - }, - { - .fd = sock_[1], - .events = POLL_IN | POLLHUP | POLLERR | POLLNVAL, - }, - }; - - while (true) { - ASSERT_THAT(poll(fds, nfds, -1), SyscallSucceeds()); - - for (int fd_idx = 0; fd_idx < nfds; ++fd_idx) { - if (fds[fd_idx].revents == 0) continue; - - ASSERT_EQ(fds[fd_idx].revents, POLL_IN); - if (fds[fd_idx].fd == sock_[1]) { - ServerHandleCommand(); - } else if (fds[fd_idx].fd == dev_fd_) { - ServerProcessFuseRequest(); - } - } - } -} - -// SetUpFuseServer creates 1 socketpair and fork the process. The parent thread -// becomes testing thread and the child thread becomes the FUSE server running -// in background. These 2 threads are connected via socketpair. sock_[0] is -// opened in testing thread and sock_[1] is opened in the FUSE server. -void FuseTest::SetUpFuseServer(const struct fuse_init_out* payload) { - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_), SyscallSucceeds()); - - switch (fork()) { - case -1: - GTEST_FAIL(); - return; - case 0: - break; - default: - ASSERT_THAT(close(sock_[1]), SyscallSucceeds()); - WaitServerComplete(); - return; - } - - // Begin child thread, i.e. the FUSE server. - ASSERT_THAT(close(sock_[0]), SyscallSucceeds()); - ServerCompleteWith(ServerConsumeFuseInit(payload).ok()); - ServerFuseLoop(); - _exit(0); -} - -void FuseTest::ServerSendData(uint32_t data) { - EXPECT_THAT(RetryEINTR(write)(sock_[1], &data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); -} - -// Reads FuseTestCmd sent from testing thread and routes to correct handler. -// Since each command should be a blocking operation, a `ServerCompleteWith()` -// is required after the switch keyword. -void FuseTest::ServerHandleCommand() { - uint32_t cmd; - EXPECT_THAT(RetryEINTR(read)(sock_[1], &cmd, sizeof(cmd)), - SyscallSucceedsWithValue(sizeof(cmd))); - - switch (static_cast<FuseTestCmd>(cmd)) { - case FuseTestCmd::kSetResponse: - ServerReceiveResponse(); - break; - case FuseTestCmd::kSetInodeLookup: - ServerReceiveInodeLookup(); - break; - case FuseTestCmd::kGetRequest: - ServerSendReceivedRequest(); - break; - case FuseTestCmd::kGetTotalReceivedBytes: - ServerSendData(static_cast<uint32_t>(requests_.UsedBytes())); - break; - case FuseTestCmd::kGetNumUnconsumedRequests: - ServerSendData(static_cast<uint32_t>(requests_.RemainingBlocks())); - break; - case FuseTestCmd::kGetNumUnsentResponses: - ServerSendData(static_cast<uint32_t>(responses_.RemainingBlocks())); - break; - case FuseTestCmd::kSkipRequest: - ServerSkipReceivedRequest(); - break; - default: - FAIL() << "Unknown FuseTestCmd " << cmd; - break; - } - - ServerCompleteWith(!HasFailure()); -} - -// Reads the expected file mode and the path of one file. Crafts a basic -// `fuse_entry_out` memory block and inserts into a map for future use. -// The FUSE server will always return this response if a FUSE_LOOKUP -// request with this specific path comes in. -void FuseTest::ServerReceiveInodeLookup() { - mode_t mode; - uint64_t size; - std::vector<char> buf(FUSE_MIN_READ_BUFFER); - - EXPECT_THAT(RetryEINTR(read)(sock_[1], &mode, sizeof(mode)), - SyscallSucceedsWithValue(sizeof(mode))); - - EXPECT_THAT(RetryEINTR(read)(sock_[1], &size, sizeof(size)), - SyscallSucceedsWithValue(sizeof(size))); - - EXPECT_THAT(RetryEINTR(read)(sock_[1], buf.data(), buf.size()), - SyscallSucceeds()); - - std::string path(buf.data()); - - uint32_t out_len = - sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out); - struct fuse_out_header out_header = { - .len = out_len, - .error = 0, - }; - struct fuse_entry_out out_payload = DefaultEntryOut(mode, nodeid_); - // Since this is only used in test, nodeid_ is simply increased by 1 to - // comply with the unqiueness of different path. - ++nodeid_; - - // Set the size. - out_payload.attr.size = size; - - memcpy(buf.data(), &out_header, sizeof(out_header)); - memcpy(buf.data() + sizeof(out_header), &out_payload, sizeof(out_payload)); - lookups_.AddMemBlock(FUSE_LOOKUP, buf.data(), out_len); - lookup_map_[path] = lookups_.Next(); -} - -// Sends the received request pointed by current cursor and advances cursor. -void FuseTest::ServerSendReceivedRequest() { - if (requests_.End()) { - FAIL() << "No more received request."; - return; - } - auto mem_block = requests_.Next(); - EXPECT_THAT( - RetryEINTR(write)(sock_[1], requests_.DataAtOffset(mem_block.offset), - mem_block.len), - SyscallSucceedsWithValue(mem_block.len)); -} - -// Skip the request pointed by current cursor. -void FuseTest::ServerSkipReceivedRequest() { - if (requests_.End()) { - FAIL() << "No more received request."; - return; - } - requests_.Next(); -} - -// Handles FUSE request. Reads request from /dev/fuse, checks if it has the -// same opcode as expected, and responds with the saved fake FUSE response. -// The FUSE request is copied to the serial buffer and can be retrieved one- -// by-one by calling GetServerActualRequest from testing thread. -void FuseTest::ServerProcessFuseRequest() { - ssize_t len; - std::vector<char> buf(FUSE_MIN_READ_BUFFER); - - // Read FUSE request. - EXPECT_THAT(len = RetryEINTR(read)(dev_fd_, buf.data(), buf.size()), - SyscallSucceeds()); - fuse_in_header* in_header = reinterpret_cast<fuse_in_header*>(buf.data()); - - // Check if this is a preset FUSE_LOOKUP path. - if (in_header->opcode == FUSE_LOOKUP) { - std::string path(buf.data() + sizeof(struct fuse_in_header)); - auto it = lookup_map_.find(path); - if (it != lookup_map_.end()) { - // Matches a preset path. Reply with fake data and skip saving the - // request. - ServerRespondFuseSuccess(lookups_, it->second, in_header->unique); - return; - } - } - - requests_.AddMemBlock(in_header->opcode, buf.data(), len); - - if (in_header->opcode == FUSE_RELEASE || in_header->opcode == FUSE_RELEASEDIR) - return; - // Check if there is a corresponding response. - if (responses_.End()) { - GTEST_NONFATAL_FAILURE_("No more FUSE response is expected"); - ServerRespondFuseError(in_header->unique); - return; - } - auto mem_block = responses_.Next(); - if (in_header->opcode != mem_block.opcode) { - std::string message = absl::StrFormat("Expect opcode %d but got %d", - mem_block.opcode, in_header->opcode); - GTEST_NONFATAL_FAILURE_(message.c_str()); - // We won't get correct response if opcode is not expected. Send error - // response here to avoid wrong parsing by VFS. - ServerRespondFuseError(in_header->unique); - return; - } - - // Write FUSE response. - ServerRespondFuseSuccess(responses_, mem_block, in_header->unique); -} - -void FuseTest::ServerRespondFuseSuccess(FuseMemBuffer& mem_buf, - const FuseMemBlock& block, - uint64_t unique) { - fuse_out_header* out_header = - reinterpret_cast<fuse_out_header*>(mem_buf.DataAtOffset(block.offset)); - - // Patch `unique` in fuse_out_header to avoid EINVAL caused by responding - // with an unknown `unique`. - out_header->unique = unique; - EXPECT_THAT(RetryEINTR(write)(dev_fd_, out_header, block.len), - SyscallSucceedsWithValue(block.len)); -} - -void FuseTest::ServerRespondFuseError(uint64_t unique) { - fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - .error = ENOSYS, - .unique = unique, - }; - EXPECT_THAT(RetryEINTR(write)(dev_fd_, &out_header, sizeof(out_header)), - SyscallSucceedsWithValue(sizeof(out_header))); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h deleted file mode 100644 index 6ad296ca2..000000000 --- a/test/fuse/linux/fuse_base.h +++ /dev/null @@ -1,251 +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. - -#ifndef GVISOR_TEST_FUSE_FUSE_BASE_H_ -#define GVISOR_TEST_FUSE_FUSE_BASE_H_ - -#include <linux/fuse.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/uio.h> - -#include <iostream> -#include <unordered_map> -#include <vector> - -#include "gtest/gtest.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" - -namespace gvisor { -namespace testing { - -constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0"; - -constexpr struct fuse_init_out kDefaultFUSEInitOutPayload = {.major = 7}; - -// Internal commands used to communicate between testing thread and the FUSE -// server. See test/fuse/README.md for further detail. -enum class FuseTestCmd { - kSetResponse = 0, - kSetInodeLookup, - kGetRequest, - kGetNumUnconsumedRequests, - kGetNumUnsentResponses, - kGetTotalReceivedBytes, - kSkipRequest, -}; - -// Holds the information of a memory block in a serial buffer. -struct FuseMemBlock { - uint32_t opcode; - size_t offset; - size_t len; -}; - -// A wrapper of a simple serial buffer that can be used with read(2) and -// write(2). Contains a cursor to indicate accessing. This class is not thread- -// safe and can only be used in single-thread version. -class FuseMemBuffer { - public: - FuseMemBuffer() : cursor_(0) { - // To read from /dev/fuse, a buffer needs at least FUSE_MIN_READ_BUFFER - // bytes to avoid EINVAL. FuseMemBuffer holds memory that can accommodate - // a sequence of FUSE request/response, so it is initiated with double - // minimal requirement. - mem_.resize(FUSE_MIN_READ_BUFFER * 2); - } - - // Returns whether there is no memory block. - bool Empty() { return blocks_.empty(); } - - // Returns if there is no more remaining memory blocks. - bool End() { return cursor_ == blocks_.size(); } - - // Returns how many bytes that have been received. - size_t UsedBytes() { - return Empty() ? 0 : blocks_.back().offset + blocks_.back().len; - } - - // Returns the available bytes remains in the serial buffer. - size_t AvailBytes() { return mem_.size() - UsedBytes(); } - - // Appends a memory block information that starts at the tail of the serial - // buffer. /dev/fuse requires at least FUSE_MIN_READ_BUFFER bytes to read, or - // it will issue EINVAL. If it is not enough, just double the buffer length. - void AddMemBlock(uint32_t opcode, void* data, size_t len) { - if (AvailBytes() < FUSE_MIN_READ_BUFFER) { - mem_.resize(mem_.size() << 1); - } - size_t offset = UsedBytes(); - memcpy(mem_.data() + offset, data, len); - blocks_.push_back(FuseMemBlock{opcode, offset, len}); - } - - // Returns the memory address at a specific offset. Used with read(2) or - // write(2). - char* DataAtOffset(size_t offset) { return mem_.data() + offset; } - - // Returns current memory block pointed by the cursor and increase by 1. - FuseMemBlock Next() { - if (End()) { - std::cerr << "Buffer is already exhausted." << std::endl; - return FuseMemBlock{}; - } - return blocks_[cursor_++]; - } - - // Returns the number of the blocks that has not been requested. - size_t RemainingBlocks() { return blocks_.size() - cursor_; } - - private: - size_t cursor_; - std::vector<FuseMemBlock> blocks_; - std::vector<char> mem_; -}; - -// FuseTest base class is useful in FUSE integration test. Inherit this class -// to automatically set up a fake FUSE server and use the member functions -// to manipulate with it. Refer to test/fuse/README.md for detailed explanation. -class FuseTest : public ::testing::Test { - public: - // nodeid_ is the ID of a fake inode. We starts from 2 since 1 is occupied by - // the mount point. - FuseTest() : nodeid_(2) {} - void SetUp() override; - void TearDown() override; - - // Called by the testing thread to set up a fake response for an expected - // opcode via socket. This can be used multiple times to define a sequence of - // expected FUSE reactions. - void SetServerResponse(uint32_t opcode, std::vector<struct iovec>& iovecs); - - // Called by the testing thread to install a fake path under the mount point. - // e.g. a file under /mnt/dir/file and moint point is /mnt, then it will look - // up "dir/file" in this case. - // - // It sets a fixed response to the FUSE_LOOKUP requests issued with this - // path, pretending there is an inode and avoid ENOENT when testing. If mode - // is not given, it creates a regular file with mode 0600. - void SetServerInodeLookup(const std::string& path, - mode_t mode = S_IFREG | S_IRUSR | S_IWUSR, - uint64_t size = 512); - - // Called by the testing thread to ask the FUSE server for its next received - // FUSE request. Be sure to use the corresponding struct of iovec to receive - // data from server. - void GetServerActualRequest(std::vector<struct iovec>& iovecs); - - // Called by the testing thread to query the number of unconsumed requests in - // the requests_ serial buffer of the FUSE server. TearDown() ensures all - // FUSE requests received by the FUSE server were consumed by the testing - // thread. - uint32_t GetServerNumUnconsumedRequests(); - - // Called by the testing thread to query the number of unsent responses in - // the responses_ serial buffer of the FUSE server. TearDown() ensures all - // preset FUSE responses were sent out by the FUSE server. - uint32_t GetServerNumUnsentResponses(); - - // Called by the testing thread to ask the FUSE server for its total received - // bytes from /dev/fuse. - uint32_t GetServerTotalReceivedBytes(); - - // Called by the testing thread to ask the FUSE server to skip stored - // request data. - void SkipServerActualRequest(); - - protected: - TempPath mount_point_; - - // Opens /dev/fuse and inherit the file descriptor for the FUSE server. - void MountFuse(const char* mountOpts = kMountOpts); - - // Creates a socketpair for communication and forks FUSE server. - void SetUpFuseServer( - const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload); - - // Unmounts the mountpoint of the FUSE server. - void UnmountFuse(); - - private: - // Sends a FuseTestCmd and gets a uint32_t data from the FUSE server. - inline uint32_t GetServerData(uint32_t cmd); - - // Waits for FUSE server to complete its processing. Complains if the FUSE - // server responds any failure during tests. - void WaitServerComplete(); - - // The FUSE server stays here and waits next command or FUSE request until it - // is terminated. - void ServerFuseLoop(); - - // Used by the FUSE server to tell testing thread if it is OK to proceed next - // command. Will be issued after processing each FuseTestCmd. - void ServerCompleteWith(bool success); - - // Consumes the first FUSE request when mounting FUSE. Replies with a - // response with empty payload. - PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload); - - // A command switch that dispatch different FuseTestCmd to its handler. - void ServerHandleCommand(); - - // The FUSE server side's corresponding code of `SetServerResponse()`. - // Handles `kSetResponse` command. Saves the fake response into its output - // memory queue. - void ServerReceiveResponse(); - - // The FUSE server side's corresponding code of `SetServerInodeLookup()`. - // Handles `kSetInodeLookup` command. Receives an expected file mode and - // file path under the mount point. - void ServerReceiveInodeLookup(); - - // The FUSE server side's corresponding code of `GetServerActualRequest()`. - // Handles `kGetRequest` command. Sends the next received request pointed by - // the cursor. - void ServerSendReceivedRequest(); - - // Sends a uint32_t data via socket. - inline void ServerSendData(uint32_t data); - - // The FUSE server side's corresponding code of `SkipServerActualRequest()`. - // Handles `kSkipRequest` command. Skip the request pointed by current cursor. - void ServerSkipReceivedRequest(); - - // Handles FUSE request sent to /dev/fuse by its saved responses. - void ServerProcessFuseRequest(); - - // Responds to FUSE request with a saved data. - void ServerRespondFuseSuccess(FuseMemBuffer& mem_buf, - const FuseMemBlock& block, uint64_t unique); - - // Responds an error header to /dev/fuse when bad thing happens. - void ServerRespondFuseError(uint64_t unique); - - int dev_fd_; - int sock_[2]; - - uint64_t nodeid_; - std::unordered_map<std::string, FuseMemBlock> lookup_map_; - - FuseMemBuffer requests_; - FuseMemBuffer responses_; - FuseMemBuffer lookups_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_FUSE_FUSE_BASE_H_ diff --git a/test/fuse/linux/fuse_fd_util.cc b/test/fuse/linux/fuse_fd_util.cc deleted file mode 100644 index 30d1157bb..000000000 --- a/test/fuse/linux/fuse_fd_util.cc +++ /dev/null @@ -1,61 +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. - -#include "test/fuse/linux/fuse_fd_util.h" - -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/uio.h> - -#include <string> -#include <vector> - -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fuse_util.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<FileDescriptor> FuseFdTest::OpenPath(const std::string &path, - uint32_t flags, uint64_t fh) { - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - struct fuse_open_out out_payload = { - .fh = fh, - .open_flags = flags, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_OPEN, iov_out); - - auto res = Open(path.c_str(), flags); - if (res.ok()) { - SkipServerActualRequest(); - } - return res; -} - -Cleanup FuseFdTest::CloseFD(FileDescriptor &fd) { - return Cleanup([&] { - close(fd.release()); - SkipServerActualRequest(); - }); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/fuse_fd_util.h b/test/fuse/linux/fuse_fd_util.h deleted file mode 100644 index 066185c94..000000000 --- a/test/fuse/linux/fuse_fd_util.h +++ /dev/null @@ -1,48 +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. - -#ifndef GVISOR_TEST_FUSE_FUSE_FD_UTIL_H_ -#define GVISOR_TEST_FUSE_FUSE_FD_UTIL_H_ - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <string> - -#include "test/fuse/linux/fuse_base.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -class FuseFdTest : public FuseTest { - public: - // Sets the FUSE server to respond to a FUSE_OPEN with corresponding flags and - // fh. Then does a real file system open on the absolute path to get an fd. - PosixErrorOr<FileDescriptor> OpenPath(const std::string &path, - uint32_t flags = O_RDONLY, - uint64_t fh = 1); - - // Returns a cleanup object that closes the fd when it is destroyed. After - // the close is done, tells the FUSE server to skip this FUSE_RELEASE. - Cleanup CloseFD(FileDescriptor &fd); -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_FUSE_FUSE_FD_UTIL_H_ diff --git a/test/fuse/linux/mkdir_test.cc b/test/fuse/linux/mkdir_test.cc deleted file mode 100644 index 9647cb93f..000000000 --- a/test/fuse/linux/mkdir_test.cc +++ /dev/null @@ -1,88 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class MkdirTest : public FuseTest { - protected: - const std::string test_dir_ = "test_dir"; - const mode_t perms_ = S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(MkdirTest, CreateDir) { - const std::string test_dir_path_ = - JoinPath(mount_point_.path().c_str(), test_dir_); - const mode_t new_umask = 0077; - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFDIR | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_MKDIR, iov_out); - TempUmask mask(new_umask); - ASSERT_THAT(mkdir(test_dir_path_.c_str(), 0777), SyscallSucceeds()); - - struct fuse_in_header in_header; - struct fuse_mkdir_in in_payload; - std::vector<char> actual_dir(test_dir_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, in_payload, actual_dir); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, - sizeof(in_header) + sizeof(in_payload) + test_dir_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_MKDIR); - EXPECT_EQ(in_payload.mode & 0777, perms_ & ~new_umask); - EXPECT_EQ(in_payload.umask, new_umask); - EXPECT_EQ(std::string(actual_dir.data()), test_dir_); -} - -TEST_F(MkdirTest, FileTypeError) { - const std::string test_dir_path_ = - JoinPath(mount_point_.path().c_str(), test_dir_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFREG | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_MKDIR, iov_out); - ASSERT_THAT(mkdir(test_dir_path_.c_str(), 0777), SyscallFailsWithErrno(EIO)); - SkipServerActualRequest(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/mknod_test.cc b/test/fuse/linux/mknod_test.cc deleted file mode 100644 index 74c74d76b..000000000 --- a/test/fuse/linux/mknod_test.cc +++ /dev/null @@ -1,107 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class MknodTest : public FuseTest { - protected: - const std::string test_file_ = "test_file"; - const mode_t perms_ = S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(MknodTest, RegularFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - const mode_t new_umask = 0077; - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFREG | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_MKNOD, iov_out); - TempUmask mask(new_umask); - ASSERT_THAT(mknod(test_file_path.c_str(), perms_, 0), SyscallSucceeds()); - - struct fuse_in_header in_header; - struct fuse_mknod_in in_payload; - std::vector<char> actual_file(test_file_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, in_payload, actual_file); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, - sizeof(in_header) + sizeof(in_payload) + test_file_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_MKNOD); - EXPECT_EQ(in_payload.mode & 0777, perms_ & ~new_umask); - EXPECT_EQ(in_payload.umask, new_umask); - EXPECT_EQ(in_payload.rdev, 0); - EXPECT_EQ(std::string(actual_file.data()), test_file_); -} - -TEST_F(MknodTest, FileTypeError) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - // server return directory instead of regular file should cause an error. - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFDIR | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_MKNOD, iov_out); - ASSERT_THAT(mknod(test_file_path.c_str(), perms_, 0), - SyscallFailsWithErrno(EIO)); - SkipServerActualRequest(); -} - -TEST_F(MknodTest, NodeIDError) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = - DefaultEntryOut(S_IFREG | perms_, FUSE_ROOT_ID); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_MKNOD, iov_out); - ASSERT_THAT(mknod(test_file_path.c_str(), perms_, 0), - SyscallFailsWithErrno(EIO)); - SkipServerActualRequest(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc deleted file mode 100644 index 276f842ea..000000000 --- a/test/fuse/linux/mount_test.cc +++ /dev/null @@ -1,86 +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. - -#include <errno.h> -#include <fcntl.h> -#include <sys/mount.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/mount_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(FuseMount, Success) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); - std::string mopts = - absl::StrFormat("fd=%d,user_id=%d,group_id=%d,rootmode=0777", fd.get(), - getuid(), getgid()); - - 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()); - std::string mount_opts = "fd=thiscantbeparsed"; - TempPath mount_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(mount("fuse", mount_dir.path().c_str(), "fuse", - MS_NODEV | MS_NOSUID, mount_opts.c_str()), - SyscallFailsWithErrno(EINVAL)); -} - -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 -} // namespace gvisor diff --git a/test/fuse/linux/open_test.cc b/test/fuse/linux/open_test.cc deleted file mode 100644 index 4b0c4a805..000000000 --- a/test/fuse/linux/open_test.cc +++ /dev/null @@ -1,128 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class OpenTest : public FuseTest { - // OpenTest doesn't care the release request when close a fd, - // so doesn't check leftover requests when tearing down. - void TearDown() { UnmountFuse(); } - - protected: - const std::string test_file_ = "test_file"; - const mode_t regular_file_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; - - struct fuse_open_out out_payload_ = { - .fh = 1, - .open_flags = O_RDWR, - }; -}; - -TEST_F(OpenTest, RegularFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, regular_file_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload_); - SetServerResponse(FUSE_OPEN, iov_out); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR)); - - struct fuse_in_header in_header; - struct fuse_open_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_OPEN); - EXPECT_EQ(in_payload.flags, O_RDWR); - EXPECT_THAT(fcntl(fd.get(), F_GETFL), SyscallSucceedsWithValue(O_RDWR)); -} - -TEST_F(OpenTest, SetNoOpen) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, regular_file_); - - // ENOSYS indicates open is not implemented. - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - .error = -ENOSYS, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload_); - SetServerResponse(FUSE_OPEN, iov_out); - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR)); - SkipServerActualRequest(); - - // check open doesn't send new request. - uint32_t recieved_before = GetServerTotalReceivedBytes(); - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR)); - EXPECT_EQ(GetServerTotalReceivedBytes(), recieved_before); -} - -TEST_F(OpenTest, OpenFail) { - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - .error = -ENOENT, - }; - - auto iov_out = FuseGenerateIovecs(out_header, out_payload_); - SetServerResponse(FUSE_OPENDIR, iov_out); - ASSERT_THAT(open(mount_point_.path().c_str(), O_RDWR), - SyscallFailsWithErrno(ENOENT)); - - struct fuse_in_header in_header; - struct fuse_open_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_OPENDIR); - EXPECT_EQ(in_payload.flags, O_RDWR); -} - -TEST_F(OpenTest, DirectoryFlagOnRegularFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - - SetServerInodeLookup(test_file_, regular_file_); - ASSERT_THAT(open(test_file_path.c_str(), O_RDWR | O_DIRECTORY), - SyscallFailsWithErrno(ENOTDIR)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/read_test.cc b/test/fuse/linux/read_test.cc deleted file mode 100644 index 88fc299d8..000000000 --- a/test/fuse/linux/read_test.cc +++ /dev/null @@ -1,390 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReadTest : public FuseTest { - void SetUp() override { - FuseTest::SetUp(); - test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_); - } - - // TearDown overrides the parent's function - // to skip checking the unconsumed release request at the end. - void TearDown() override { UnmountFuse(); } - - protected: - const std::string test_file_ = "test_file"; - const mode_t test_file_mode_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; - const uint64_t test_fh_ = 1; - const uint32_t open_flag_ = O_RDWR; - - std::string test_file_path_; - - PosixErrorOr<FileDescriptor> OpenTestFile(const std::string &path, - uint64_t size = 512) { - SetServerInodeLookup(test_file_, test_file_mode_, size); - - struct fuse_out_header out_header_open = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - struct fuse_open_out out_payload_open = { - .fh = test_fh_, - .open_flags = open_flag_, - }; - auto iov_out_open = FuseGenerateIovecs(out_header_open, out_payload_open); - SetServerResponse(FUSE_OPEN, iov_out_open); - - auto res = Open(path.c_str(), open_flag_); - if (res.ok()) { - SkipServerActualRequest(); - } - return res; - } -}; - -class ReadTestSmallMaxRead : public ReadTest { - void SetUp() override { - MountFuse(mountOpts); - SetUpFuseServer(); - test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_); - } - - protected: - constexpr static char mountOpts[] = - "rootmode=755,user_id=0,group_id=0,max_read=4096"; - // 4096 is hard-coded as the max_read in mount options. - const int size_fragment = 4096; -}; - -TEST_F(ReadTest, ReadWhole) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the read. - const int n_read = 5; - std::vector<char> data(n_read); - RandomizeBuffer(data.data(), data.size()); - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - SetServerResponse(FUSE_READ, iov_out_read); - - // Read the whole "file". - std::vector<char> buf(n_read); - EXPECT_THAT(read(fd.get(), buf.data(), n_read), - SyscallSucceedsWithValue(n_read)); - - // Check the read request. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, 0); - EXPECT_EQ(buf, data); -} - -TEST_F(ReadTest, ReadPartial) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the read. - const int n_data = 10; - std::vector<char> data(n_data); - RandomizeBuffer(data.data(), data.size()); - // Note: due to read ahead, current read implementation will treat any - // response that is longer than requested as correct (i.e. not reach the EOF). - // Therefore, the test below should make sure the size to read does not exceed - // n_data. - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - - std::vector<char> buf(n_data); - - // Read 1 bytes. - SetServerResponse(FUSE_READ, iov_out_read); - EXPECT_THAT(read(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1)); - - // Check the 1-byte read request. - GetServerActualRequest(iov_in); - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, 0); - - // Read 3 bytes. - SetServerResponse(FUSE_READ, iov_out_read); - EXPECT_THAT(read(fd.get(), buf.data(), 3), SyscallSucceedsWithValue(3)); - - // Check the 3-byte read request. - GetServerActualRequest(iov_in); - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_payload_read.offset, 1); - - // Read 5 bytes. - SetServerResponse(FUSE_READ, iov_out_read); - EXPECT_THAT(read(fd.get(), buf.data(), 5), SyscallSucceedsWithValue(5)); - - // Check the 5-byte read request. - GetServerActualRequest(iov_in); - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_payload_read.offset, 4); -} - -TEST_F(ReadTest, PRead) { - const int file_size = 512; - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, file_size)); - - // Prepare for the read. - const int n_read = 5; - std::vector<char> data(n_read); - RandomizeBuffer(data.data(), data.size()); - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - SetServerResponse(FUSE_READ, iov_out_read); - - // Read some bytes. - std::vector<char> buf(n_read); - const int offset_read = file_size >> 1; - EXPECT_THAT(pread(fd.get(), buf.data(), n_read, offset_read), - SyscallSucceedsWithValue(n_read)); - - // Check the read request. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, offset_read); - EXPECT_EQ(buf, data); -} - -TEST_F(ReadTest, ReadZero) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Issue the read. - std::vector<char> buf; - EXPECT_THAT(read(fd.get(), buf.data(), 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(ReadTest, ReadShort) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the short read. - const int n_read = 5; - std::vector<char> data(n_read >> 1); - RandomizeBuffer(data.data(), data.size()); - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - SetServerResponse(FUSE_READ, iov_out_read); - - // Read the whole "file". - std::vector<char> buf(n_read); - EXPECT_THAT(read(fd.get(), buf.data(), n_read), - SyscallSucceedsWithValue(data.size())); - - // Check the read request. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, 0); - std::vector<char> short_buf(buf.begin(), buf.begin() + data.size()); - EXPECT_EQ(short_buf, data); -} - -TEST_F(ReadTest, ReadShortEOF) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the short read. - struct fuse_out_header out_header_read = { - .len = static_cast<uint32_t>(sizeof(struct fuse_out_header)), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read); - SetServerResponse(FUSE_READ, iov_out_read); - - // Read the whole "file". - const int n_read = 10; - std::vector<char> buf(n_read); - EXPECT_THAT(read(fd.get(), buf.data(), n_read), SyscallSucceedsWithValue(0)); - - // Check the read request. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, 0); -} - -TEST_F(ReadTestSmallMaxRead, ReadSmallMaxRead) { - const int n_fragment = 10; - const int n_read = size_fragment * n_fragment; - - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_read)); - - // Prepare for the read. - std::vector<char> data(size_fragment); - RandomizeBuffer(data.data(), data.size()); - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - - for (int i = 0; i < n_fragment; ++i) { - SetServerResponse(FUSE_READ, iov_out_read); - } - - // Read the whole "file". - std::vector<char> buf(n_read); - EXPECT_THAT(read(fd.get(), buf.data(), n_read), - SyscallSucceedsWithValue(n_read)); - - ASSERT_EQ(GetServerNumUnsentResponses(), 0); - ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment); - - // Check each read segment. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - - for (int i = 0; i < n_fragment; ++i) { - GetServerActualRequest(iov_in); - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, i * size_fragment); - EXPECT_EQ(in_payload_read.size, size_fragment); - - auto it = buf.begin() + i * size_fragment; - EXPECT_EQ(std::vector<char>(it, it + size_fragment), data); - } -} - -TEST_F(ReadTestSmallMaxRead, ReadSmallMaxReadShort) { - const int n_fragment = 10; - const int n_read = size_fragment * n_fragment; - - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_read)); - - // Prepare for the read. - std::vector<char> data(size_fragment); - RandomizeBuffer(data.data(), data.size()); - struct fuse_out_header out_header_read = { - .len = - static_cast<uint32_t>(sizeof(struct fuse_out_header) + data.size()), - }; - auto iov_out_read = FuseGenerateIovecs(out_header_read, data); - - for (int i = 0; i < n_fragment - 1; ++i) { - SetServerResponse(FUSE_READ, iov_out_read); - } - - // The last fragment is a short read. - std::vector<char> half_data(data.begin(), data.begin() + (data.size() >> 1)); - struct fuse_out_header out_header_read_short = { - .len = static_cast<uint32_t>(sizeof(struct fuse_out_header) + - half_data.size()), - }; - auto iov_out_read_short = - FuseGenerateIovecs(out_header_read_short, half_data); - SetServerResponse(FUSE_READ, iov_out_read_short); - - // Read the whole "file". - std::vector<char> buf(n_read); - EXPECT_THAT(read(fd.get(), buf.data(), n_read), - SyscallSucceedsWithValue(n_read - (data.size() >> 1))); - - ASSERT_EQ(GetServerNumUnsentResponses(), 0); - ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment); - - // Check each read segment. - struct fuse_in_header in_header_read; - struct fuse_read_in in_payload_read; - auto iov_in = FuseGenerateIovecs(in_header_read, in_payload_read); - - for (int i = 0; i < n_fragment; ++i) { - GetServerActualRequest(iov_in); - EXPECT_EQ(in_payload_read.fh, test_fh_); - EXPECT_EQ(in_header_read.len, - sizeof(in_header_read) + sizeof(in_payload_read)); - EXPECT_EQ(in_header_read.opcode, FUSE_READ); - EXPECT_EQ(in_payload_read.offset, i * size_fragment); - EXPECT_EQ(in_payload_read.size, size_fragment); - - auto it = buf.begin() + i * size_fragment; - if (i != n_fragment - 1) { - EXPECT_EQ(std::vector<char>(it, it + data.size()), data); - } else { - EXPECT_EQ(std::vector<char>(it, it + half_data.size()), half_data); - } - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/readdir_test.cc b/test/fuse/linux/readdir_test.cc deleted file mode 100644 index 2afb4b062..000000000 --- a/test/fuse/linux/readdir_test.cc +++ /dev/null @@ -1,193 +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. - -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <linux/unistd.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) -#define FUSE_DIRENT_ALIGN(x) \ - (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) -#define FUSE_DIRENT_SIZE(d) FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) - -namespace gvisor { -namespace testing { - -namespace { - -class ReaddirTest : public FuseTest { - public: - void fill_fuse_dirent(char *buf, const char *name, uint64_t ino) { - size_t namelen = strlen(name); - size_t entlen = FUSE_NAME_OFFSET + namelen; - size_t entlen_padded = FUSE_DIRENT_ALIGN(entlen); - struct fuse_dirent *dirent; - - dirent = reinterpret_cast<struct fuse_dirent *>(buf); - dirent->ino = ino; - dirent->namelen = namelen; - memcpy(dirent->name, name, namelen); - memset(dirent->name + namelen, 0, entlen_padded - entlen); - } - - protected: - const std::string test_dir_name_ = "test_dir"; -}; - -TEST_F(ReaddirTest, SingleEntry) { - const std::string test_dir_path = - JoinPath(mount_point_.path().c_str(), test_dir_name_); - - const uint64_t ino_dir = 1024; - // We need to make sure the test dir is a directory that can be found. - mode_t expected_mode = - S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - struct fuse_attr dir_attr = { - .ino = ino_dir, - .size = 512, - .blocks = 4, - .mode = expected_mode, - .blksize = 4096, - }; - - // We need to make sure the test dir is a directory that can be found. - struct fuse_out_header lookup_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out lookup_payload = { - .nodeid = 1, - .entry_valid = true, - .attr_valid = true, - .attr = dir_attr, - }; - - struct fuse_out_header open_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - struct fuse_open_out open_payload = { - .fh = 1, - }; - auto iov_out = FuseGenerateIovecs(lookup_header, lookup_payload); - SetServerResponse(FUSE_LOOKUP, iov_out); - - iov_out = FuseGenerateIovecs(open_header, open_payload); - SetServerResponse(FUSE_OPENDIR, iov_out); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_dir_path.c_str(), O_RDONLY)); - - // The open command makes two syscalls. Lookup the dir file and open. - // We don't need to inspect those headers in this test. - SkipServerActualRequest(); // LOOKUP. - SkipServerActualRequest(); // OPENDIR. - - // Readdir test code. - std::string dot = "."; - std::string dot_dot = ".."; - std::string test_file = "testFile"; - - // Figure out how many dirents to send over and allocate them appropriately. - // Each dirent has a dynamic name and a static metadata part. The dirent size - // is aligned to being a multiple of 8. - size_t dot_file_dirent_size = - FUSE_DIRENT_ALIGN(dot.length() + FUSE_NAME_OFFSET); - size_t dot_dot_file_dirent_size = - FUSE_DIRENT_ALIGN(dot_dot.length() + FUSE_NAME_OFFSET); - size_t test_file_dirent_size = - FUSE_DIRENT_ALIGN(test_file.length() + FUSE_NAME_OFFSET); - - // Create an appropriately sized payload. - size_t readdir_payload_size = - test_file_dirent_size + dot_file_dirent_size + dot_dot_file_dirent_size; - std::vector<char> readdir_payload_vec(readdir_payload_size); - char *readdir_payload = readdir_payload_vec.data(); - - // Use fake ino for other directories. - fill_fuse_dirent(readdir_payload, dot.c_str(), ino_dir - 2); - fill_fuse_dirent(readdir_payload + dot_file_dirent_size, dot_dot.c_str(), - ino_dir - 1); - fill_fuse_dirent( - readdir_payload + dot_file_dirent_size + dot_dot_file_dirent_size, - test_file.c_str(), ino_dir); - - struct fuse_out_header readdir_header = { - .len = uint32_t(sizeof(struct fuse_out_header) + readdir_payload_size), - }; - struct fuse_out_header readdir_header_break = { - .len = uint32_t(sizeof(struct fuse_out_header)), - }; - - iov_out = FuseGenerateIovecs(readdir_header, readdir_payload_vec); - SetServerResponse(FUSE_READDIR, iov_out); - - iov_out = FuseGenerateIovecs(readdir_header_break); - SetServerResponse(FUSE_READDIR, iov_out); - - std::vector<char> buf(4090, 0); - int nread, off = 0, i = 0; - EXPECT_THAT( - nread = syscall(__NR_getdents64, fd.get(), buf.data(), buf.size()), - SyscallSucceeds()); - for (; off < nread;) { - struct dirent64 *ent = (struct dirent64 *)(buf.data() + off); - off += ent->d_reclen; - switch (i++) { - case 0: - EXPECT_EQ(std::string(ent->d_name), dot); - break; - case 1: - EXPECT_EQ(std::string(ent->d_name), dot_dot); - break; - case 2: - EXPECT_EQ(std::string(ent->d_name), test_file); - break; - } - } - - EXPECT_THAT( - nread = syscall(__NR_getdents64, fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(0)); - - SkipServerActualRequest(); // READDIR. - SkipServerActualRequest(); // READDIR with no data. - - // Clean up. - fd.reset(-1); - - struct fuse_in_header in_header; - struct fuse_release_in in_payload; - - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_RELEASEDIR); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/readlink_test.cc b/test/fuse/linux/readlink_test.cc deleted file mode 100644 index 2cba8fc23..000000000 --- a/test/fuse/linux/readlink_test.cc +++ /dev/null @@ -1,85 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReadlinkTest : public FuseTest { - protected: - const std::string test_file_ = "test_file_"; - const mode_t perms_ = S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(ReadlinkTest, ReadSymLink) { - const std::string symlink_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, S_IFLNK | perms_); - - struct fuse_out_header out_header = { - .len = static_cast<uint32_t>(sizeof(struct fuse_out_header)) + - static_cast<uint32_t>(test_file_.length()) + 1, - }; - std::string link = test_file_; - auto iov_out = FuseGenerateIovecs(out_header, link); - SetServerResponse(FUSE_READLINK, iov_out); - const std::string actual_link = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink(symlink_path)); - - struct fuse_in_header in_header; - auto iov_in = FuseGenerateIovecs(in_header); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header)); - EXPECT_EQ(in_header.opcode, FUSE_READLINK); - EXPECT_EQ(0, memcmp(actual_link.c_str(), link.data(), link.size())); - - // next readlink should have link cached, so shouldn't have new request to - // server. - uint32_t recieved_before = GetServerTotalReceivedBytes(); - ASSERT_NO_ERRNO(ReadLink(symlink_path)); - EXPECT_EQ(GetServerTotalReceivedBytes(), recieved_before); -} - -TEST_F(ReadlinkTest, NotSymlink) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, S_IFREG | perms_); - - std::vector<char> buf(PATH_MAX + 1); - ASSERT_THAT(readlink(test_file_path.c_str(), buf.data(), PATH_MAX), - SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/release_test.cc b/test/fuse/linux/release_test.cc deleted file mode 100644 index b5adb0870..000000000 --- a/test/fuse/linux/release_test.cc +++ /dev/null @@ -1,74 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReleaseTest : public FuseTest { - protected: - const std::string test_file_ = "test_file"; -}; - -TEST_F(ReleaseTest, RegularFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - struct fuse_open_out out_payload = { - .fh = 1, - .open_flags = O_RDWR, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_OPEN, iov_out); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path, O_RDWR)); - SkipServerActualRequest(); - ASSERT_THAT(close(fd.release()), SyscallSucceeds()); - - struct fuse_in_header in_header; - struct fuse_release_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_RELEASE); - EXPECT_EQ(in_payload.flags, O_RDWR); - EXPECT_EQ(in_payload.fh, 1); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/rmdir_test.cc b/test/fuse/linux/rmdir_test.cc deleted file mode 100644 index e3200e446..000000000 --- a/test/fuse/linux/rmdir_test.cc +++ /dev/null @@ -1,100 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fs_util.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class RmDirTest : public FuseTest { - protected: - const std::string test_dir_name_ = "test_dir"; - const std::string test_subdir_ = "test_subdir"; - const mode_t test_dir_mode_ = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(RmDirTest, NormalRmDir) { - const std::string test_dir_path_ = - JoinPath(mount_point_.path().c_str(), test_dir_name_); - - SetServerInodeLookup(test_dir_name_, test_dir_mode_); - - // RmDir code. - struct fuse_out_header rmdir_header = { - .len = sizeof(struct fuse_out_header), - }; - - auto iov_out = FuseGenerateIovecs(rmdir_header); - SetServerResponse(FUSE_RMDIR, iov_out); - - ASSERT_THAT(rmdir(test_dir_path_.c_str()), SyscallSucceeds()); - - struct fuse_in_header in_header; - std::vector<char> actual_dirname(test_dir_name_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, actual_dirname); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + test_dir_name_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_RMDIR); - EXPECT_EQ(std::string(actual_dirname.data()), test_dir_name_); -} - -TEST_F(RmDirTest, NormalRmDirSubdir) { - SetServerInodeLookup(test_subdir_, S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO); - const std::string test_dir_path_ = - JoinPath(mount_point_.path().c_str(), test_subdir_, test_dir_name_); - SetServerInodeLookup(test_dir_name_, test_dir_mode_); - - // RmDir code. - struct fuse_out_header rmdir_header = { - .len = sizeof(struct fuse_out_header), - }; - - auto iov_out = FuseGenerateIovecs(rmdir_header); - SetServerResponse(FUSE_RMDIR, iov_out); - - ASSERT_THAT(rmdir(test_dir_path_.c_str()), SyscallSucceeds()); - - struct fuse_in_header in_header; - std::vector<char> actual_dirname(test_dir_name_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, actual_dirname); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + test_dir_name_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_RMDIR); - EXPECT_EQ(std::string(actual_dirname.data()), test_dir_name_); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/setstat_test.cc b/test/fuse/linux/setstat_test.cc deleted file mode 100644 index 68301c775..000000000 --- a/test/fuse/linux/setstat_test.cc +++ /dev/null @@ -1,338 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> -#include <utime.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_fd_util.h" -#include "test/util/cleanup.h" -#include "test/util/fs_util.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class SetStatTest : public FuseFdTest { - public: - void SetUp() override { - FuseFdTest::SetUp(); - test_dir_path_ = JoinPath(mount_point_.path(), test_dir_); - test_file_path_ = JoinPath(mount_point_.path(), test_file_); - } - - protected: - const uint64_t fh = 23; - const std::string test_dir_ = "testdir"; - const std::string test_file_ = "testfile"; - const mode_t test_dir_mode_ = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR; - const mode_t test_file_mode_ = S_IFREG | S_IRUSR | S_IWUSR | S_IXUSR; - - std::string test_dir_path_; - std::string test_file_path_; -}; - -TEST_F(SetStatTest, ChmodDir) { - // Set up fixture. - SetServerInodeLookup(test_dir_, test_dir_mode_); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - mode_t set_mode = S_IRGRP | S_IWGRP | S_IXGRP; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(set_mode, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(chmod(test_dir_path_.c_str(), set_mode), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_MODE); - EXPECT_EQ(in_payload.mode, S_IFDIR | set_mode); -} - -TEST_F(SetStatTest, ChownDir) { - // Set up fixture. - SetServerInodeLookup(test_dir_, test_dir_mode_); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(test_dir_mode_, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(chown(test_dir_path_.c_str(), 1025, 1025), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_UID | FATTR_GID); - EXPECT_EQ(in_payload.uid, 1025); - EXPECT_EQ(in_payload.gid, 1025); -} - -TEST_F(SetStatTest, TruncateFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(truncate(test_file_path_.c_str(), 321), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_SIZE); - EXPECT_EQ(in_payload.size, 321); -} - -TEST_F(SetStatTest, UtimeFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - time_t expected_atime = 1597159766, expected_mtime = 1597159765; - struct utimbuf times = { - .actime = expected_atime, - .modtime = expected_mtime, - }; - EXPECT_THAT(utime(test_file_path_.c_str(), ×), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_ATIME | FATTR_MTIME); - EXPECT_EQ(in_payload.atime, expected_atime); - EXPECT_EQ(in_payload.mtime, expected_mtime); -} - -TEST_F(SetStatTest, UtimesFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(test_file_mode_, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - struct timeval expected_times[2] = { - { - .tv_sec = 1597159766, - .tv_usec = 234945, - }, - { - .tv_sec = 1597159765, - .tv_usec = 232341, - }, - }; - EXPECT_THAT(utimes(test_file_path_.c_str(), expected_times), - SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_ATIME | FATTR_MTIME); - EXPECT_EQ(in_payload.atime, expected_times[0].tv_sec); - EXPECT_EQ(in_payload.atimensec, expected_times[0].tv_usec * 1000); - EXPECT_EQ(in_payload.mtime, expected_times[1].tv_sec); - EXPECT_EQ(in_payload.mtimensec, expected_times[1].tv_usec * 1000); -} - -TEST_F(SetStatTest, FtruncateFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh)); - auto close_fd = CloseFD(fd); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(test_file_mode_, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(ftruncate(fd.get(), 321), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_SIZE | FATTR_FH); - EXPECT_EQ(in_payload.fh, fh); - EXPECT_EQ(in_payload.size, 321); -} - -TEST_F(SetStatTest, FchmodFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh)); - auto close_fd = CloseFD(fd); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - mode_t set_mode = S_IROTH | S_IWOTH | S_IXOTH; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(set_mode, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(fchmod(fd.get(), set_mode), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_MODE | FATTR_FH); - EXPECT_EQ(in_payload.fh, fh); - EXPECT_EQ(in_payload.mode, S_IFREG | set_mode); -} - -TEST_F(SetStatTest, FchownFile) { - // Set up fixture. - SetServerInodeLookup(test_file_, test_file_mode_); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh)); - auto close_fd = CloseFD(fd); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - }; - struct fuse_attr_out out_payload = { - .attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR | S_IXUSR, 2), - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SETATTR, iov_out); - - // Make syscall. - EXPECT_THAT(fchown(fd.get(), 1025, 1025), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_setattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload)); - EXPECT_EQ(in_header.opcode, FUSE_SETATTR); - EXPECT_EQ(in_header.uid, 0); - EXPECT_EQ(in_header.gid, 0); - EXPECT_EQ(in_payload.valid, FATTR_UID | FATTR_GID | FATTR_FH); - EXPECT_EQ(in_payload.fh, fh); - EXPECT_EQ(in_payload.uid, 1025); - EXPECT_EQ(in_payload.gid, 1025); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc deleted file mode 100644 index 73321592b..000000000 --- a/test/fuse/linux/stat_test.cc +++ /dev/null @@ -1,229 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_fd_util.h" -#include "test/util/cleanup.h" -#include "test/util/fs_util.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class StatTest : public FuseFdTest { - public: - void SetUp() override { - FuseFdTest::SetUp(); - test_file_path_ = JoinPath(mount_point_.path(), test_file_); - } - - protected: - bool StatsAreEqual(struct stat expected, struct stat actual) { - // Device number will be dynamically allocated by kernel, we cannot know in - // advance. - actual.st_dev = expected.st_dev; - return memcmp(&expected, &actual, sizeof(struct stat)) == 0; - } - - const std::string test_file_ = "testfile"; - const mode_t expected_mode = S_IFREG | S_IRUSR | S_IWUSR; - const uint64_t fh = 23; - - std::string test_file_path_; -}; - -TEST_F(StatTest, StatNormal) { - // Set up fixture. - struct fuse_attr attr = DefaultFuseAttr(expected_mode, 1); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - }; - struct fuse_attr_out out_payload = { - .attr = attr, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_GETATTR, iov_out); - - // Make syscall. - struct stat stat_buf; - EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf), SyscallSucceeds()); - - // Check filesystem operation result. - struct stat expected_stat = { - .st_ino = attr.ino, -#ifdef __aarch64__ - .st_mode = expected_mode, - .st_nlink = attr.nlink, -#else - .st_nlink = attr.nlink, - .st_mode = expected_mode, -#endif - .st_uid = attr.uid, - .st_gid = attr.gid, - .st_rdev = attr.rdev, - .st_size = static_cast<off_t>(attr.size), - .st_blksize = attr.blksize, - .st_blocks = static_cast<blkcnt_t>(attr.blocks), - .st_atim = (struct timespec){.tv_sec = static_cast<int>(attr.atime), - .tv_nsec = attr.atimensec}, - .st_mtim = (struct timespec){.tv_sec = static_cast<int>(attr.mtime), - .tv_nsec = attr.mtimensec}, - .st_ctim = (struct timespec){.tv_sec = static_cast<int>(attr.ctime), - .tv_nsec = attr.ctimensec}, - }; - EXPECT_TRUE(StatsAreEqual(stat_buf, expected_stat)); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_getattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.opcode, FUSE_GETATTR); - EXPECT_EQ(in_payload.getattr_flags, 0); - EXPECT_EQ(in_payload.fh, 0); -} - -TEST_F(StatTest, StatNotFound) { - // Set up fixture. - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - .error = -ENOENT, - }; - auto iov_out = FuseGenerateIovecs(out_header); - SetServerResponse(FUSE_GETATTR, iov_out); - - // Make syscall. - struct stat stat_buf; - EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf), - SyscallFailsWithErrno(ENOENT)); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_getattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.opcode, FUSE_GETATTR); - EXPECT_EQ(in_payload.getattr_flags, 0); - EXPECT_EQ(in_payload.fh, 0); -} - -TEST_F(StatTest, FstatNormal) { - // Set up fixture. - SetServerInodeLookup(test_file_); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDONLY, fh)); - auto close_fd = CloseFD(fd); - - struct fuse_attr attr = DefaultFuseAttr(expected_mode, 2); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - }; - struct fuse_attr_out out_payload = { - .attr = attr, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_GETATTR, iov_out); - - // Make syscall. - struct stat stat_buf; - EXPECT_THAT(fstat(fd.get(), &stat_buf), SyscallSucceeds()); - - // Check filesystem operation result. - struct stat expected_stat = { - .st_ino = attr.ino, -#ifdef __aarch64__ - .st_mode = expected_mode, - .st_nlink = attr.nlink, -#else - .st_nlink = attr.nlink, - .st_mode = expected_mode, -#endif - .st_uid = attr.uid, - .st_gid = attr.gid, - .st_rdev = attr.rdev, - .st_size = static_cast<off_t>(attr.size), - .st_blksize = attr.blksize, - .st_blocks = static_cast<blkcnt_t>(attr.blocks), - .st_atim = (struct timespec){.tv_sec = static_cast<int>(attr.atime), - .tv_nsec = attr.atimensec}, - .st_mtim = (struct timespec){.tv_sec = static_cast<int>(attr.mtime), - .tv_nsec = attr.mtimensec}, - .st_ctim = (struct timespec){.tv_sec = static_cast<int>(attr.ctime), - .tv_nsec = attr.ctimensec}, - }; - EXPECT_TRUE(StatsAreEqual(stat_buf, expected_stat)); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_getattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.opcode, FUSE_GETATTR); - EXPECT_EQ(in_payload.getattr_flags, 0); - EXPECT_EQ(in_payload.fh, 0); -} - -TEST_F(StatTest, StatByFileHandle) { - // Set up fixture. - SetServerInodeLookup(test_file_, expected_mode, 0); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDONLY, fh)); - auto close_fd = CloseFD(fd); - - struct fuse_attr attr = DefaultFuseAttr(expected_mode, 2, 0); - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - }; - struct fuse_attr_out out_payload = { - .attr = attr, - }; - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_GETATTR, iov_out); - - // Make syscall. - std::vector<char> buf(1); - // Since this is an empty file, it won't issue FUSE_READ. But a FUSE_GETATTR - // will be issued before read completes. - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), SyscallSucceeds()); - - // Check FUSE request. - struct fuse_in_header in_header; - struct fuse_getattr_in in_payload; - auto iov_in = FuseGenerateIovecs(in_header, in_payload); - - GetServerActualRequest(iov_in); - EXPECT_EQ(in_header.opcode, FUSE_GETATTR); - EXPECT_EQ(in_payload.getattr_flags, FUSE_GETATTR_FH); - EXPECT_EQ(in_payload.fh, fh); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/symlink_test.cc b/test/fuse/linux/symlink_test.cc deleted file mode 100644 index 2c3a52987..000000000 --- a/test/fuse/linux/symlink_test.cc +++ /dev/null @@ -1,88 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class SymlinkTest : public FuseTest { - protected: - const std::string target_file_ = "target_file_"; - const std::string symlink_ = "symlink_"; - const mode_t perms_ = S_IRWXU | S_IRWXG | S_IRWXO; -}; - -TEST_F(SymlinkTest, CreateSymLink) { - const std::string symlink_path = - JoinPath(mount_point_.path().c_str(), symlink_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFLNK | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SYMLINK, iov_out); - ASSERT_THAT(symlink(target_file_.c_str(), symlink_path.c_str()), - SyscallSucceeds()); - - struct fuse_in_header in_header; - std::vector<char> actual_target_file(target_file_.length() + 1); - std::vector<char> actual_symlink(symlink_.length() + 1); - auto iov_in = - FuseGenerateIovecs(in_header, actual_symlink, actual_target_file); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, - sizeof(in_header) + symlink_.length() + target_file_.length() + 2); - EXPECT_EQ(in_header.opcode, FUSE_SYMLINK); - EXPECT_EQ(std::string(actual_target_file.data()), target_file_); - EXPECT_EQ(std::string(actual_symlink.data()), symlink_); -} - -TEST_F(SymlinkTest, FileTypeError) { - const std::string symlink_path = - JoinPath(mount_point_.path().c_str(), symlink_); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out), - }; - struct fuse_entry_out out_payload = DefaultEntryOut(S_IFREG | perms_, 5); - auto iov_out = FuseGenerateIovecs(out_header, out_payload); - SetServerResponse(FUSE_SYMLINK, iov_out); - ASSERT_THAT(symlink(target_file_.c_str(), symlink_path.c_str()), - SyscallFailsWithErrno(EIO)); - SkipServerActualRequest(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/unlink_test.cc b/test/fuse/linux/unlink_test.cc deleted file mode 100644 index 13efbf7c7..000000000 --- a/test/fuse/linux/unlink_test.cc +++ /dev/null @@ -1,107 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class UnlinkTest : public FuseTest { - protected: - const std::string test_file_ = "test_file"; - const std::string test_subdir_ = "test_subdir"; -}; - -TEST_F(UnlinkTest, RegularFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - }; - auto iov_out = FuseGenerateIovecs(out_header); - SetServerResponse(FUSE_UNLINK, iov_out); - - ASSERT_THAT(unlink(test_file_path.c_str()), SyscallSucceeds()); - struct fuse_in_header in_header; - std::vector<char> unlinked_file(test_file_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, unlinked_file); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + test_file_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_UNLINK); - EXPECT_EQ(std::string(unlinked_file.data()), test_file_); -} - -TEST_F(UnlinkTest, RegularFileSubDir) { - SetServerInodeLookup(test_subdir_, S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO); - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_subdir_, test_file_); - SetServerInodeLookup(test_file_, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - }; - auto iov_out = FuseGenerateIovecs(out_header); - SetServerResponse(FUSE_UNLINK, iov_out); - - ASSERT_THAT(unlink(test_file_path.c_str()), SyscallSucceeds()); - struct fuse_in_header in_header; - std::vector<char> unlinked_file(test_file_.length() + 1); - auto iov_in = FuseGenerateIovecs(in_header, unlinked_file); - GetServerActualRequest(iov_in); - - EXPECT_EQ(in_header.len, sizeof(in_header) + test_file_.length() + 1); - EXPECT_EQ(in_header.opcode, FUSE_UNLINK); - EXPECT_EQ(std::string(unlinked_file.data()), test_file_); -} - -TEST_F(UnlinkTest, NoFile) { - const std::string test_file_path = - JoinPath(mount_point_.path().c_str(), test_file_); - SetServerInodeLookup(test_file_, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO); - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - .error = -ENOENT, - }; - auto iov_out = FuseGenerateIovecs(out_header); - SetServerResponse(FUSE_UNLINK, iov_out); - - ASSERT_THAT(unlink(test_file_path.c_str()), SyscallFailsWithErrno(ENOENT)); - SkipServerActualRequest(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/write_test.cc b/test/fuse/linux/write_test.cc deleted file mode 100644 index 1a62beb96..000000000 --- a/test/fuse/linux/write_test.cc +++ /dev/null @@ -1,303 +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. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/fuse_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class WriteTest : public FuseTest { - void SetUp() override { - FuseTest::SetUp(); - test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_); - } - - // TearDown overrides the parent's function - // to skip checking the unconsumed release request at the end. - void TearDown() override { UnmountFuse(); } - - protected: - const std::string test_file_ = "test_file"; - const mode_t test_file_mode_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; - const uint64_t test_fh_ = 1; - const uint32_t open_flag_ = O_RDWR; - - std::string test_file_path_; - - PosixErrorOr<FileDescriptor> OpenTestFile(const std::string &path, - uint64_t size = 512) { - SetServerInodeLookup(test_file_, test_file_mode_, size); - - struct fuse_out_header out_header_open = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out), - }; - struct fuse_open_out out_payload_open = { - .fh = test_fh_, - .open_flags = open_flag_, - }; - auto iov_out_open = FuseGenerateIovecs(out_header_open, out_payload_open); - SetServerResponse(FUSE_OPEN, iov_out_open); - - auto res = Open(path.c_str(), open_flag_); - if (res.ok()) { - SkipServerActualRequest(); - } - return res; - } -}; - -class WriteTestSmallMaxWrite : public WriteTest { - void SetUp() override { - MountFuse(); - SetUpFuseServer(&fuse_init_payload); - test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_); - } - - protected: - const static uint32_t max_write_ = 4096; - constexpr static struct fuse_init_out fuse_init_payload = { - .major = 7, - .max_write = max_write_, - }; - - const uint32_t size_fragment = max_write_; -}; - -TEST_F(WriteTest, WriteNormal) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the write. - const int n_write = 10; - struct fuse_out_header out_header_write = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out), - }; - struct fuse_write_out out_payload_write = { - .size = n_write, - }; - auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write); - SetServerResponse(FUSE_WRITE, iov_out_write); - - // Issue the write. - std::vector<char> buf(n_write); - RandomizeBuffer(buf.data(), buf.size()); - EXPECT_THAT(write(fd.get(), buf.data(), n_write), - SyscallSucceedsWithValue(n_write)); - - // Check the write request. - struct fuse_in_header in_header_write; - struct fuse_write_in in_payload_write; - std::vector<char> payload_buf(n_write); - auto iov_in_write = - FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf); - GetServerActualRequest(iov_in_write); - - EXPECT_EQ(in_payload_write.fh, test_fh_); - EXPECT_EQ(in_header_write.len, - sizeof(in_header_write) + sizeof(in_payload_write)); - EXPECT_EQ(in_header_write.opcode, FUSE_WRITE); - EXPECT_EQ(in_payload_write.offset, 0); - EXPECT_EQ(in_payload_write.size, n_write); - EXPECT_EQ(buf, payload_buf); -} - -TEST_F(WriteTest, WriteShort) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the write. - const int n_write = 10, n_written = 5; - struct fuse_out_header out_header_write = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out), - }; - struct fuse_write_out out_payload_write = { - .size = n_written, - }; - auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write); - SetServerResponse(FUSE_WRITE, iov_out_write); - - // Issue the write. - std::vector<char> buf(n_write); - RandomizeBuffer(buf.data(), buf.size()); - EXPECT_THAT(write(fd.get(), buf.data(), n_write), - SyscallSucceedsWithValue(n_written)); - - // Check the write request. - struct fuse_in_header in_header_write; - struct fuse_write_in in_payload_write; - std::vector<char> payload_buf(n_write); - auto iov_in_write = - FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf); - GetServerActualRequest(iov_in_write); - - EXPECT_EQ(in_payload_write.fh, test_fh_); - EXPECT_EQ(in_header_write.len, - sizeof(in_header_write) + sizeof(in_payload_write)); - EXPECT_EQ(in_header_write.opcode, FUSE_WRITE); - EXPECT_EQ(in_payload_write.offset, 0); - EXPECT_EQ(in_payload_write.size, n_write); - EXPECT_EQ(buf, payload_buf); -} - -TEST_F(WriteTest, WriteShortZero) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Prepare for the write. - const int n_write = 10; - struct fuse_out_header out_header_write = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out), - }; - struct fuse_write_out out_payload_write = { - .size = 0, - }; - auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write); - SetServerResponse(FUSE_WRITE, iov_out_write); - - // Issue the write. - std::vector<char> buf(n_write); - RandomizeBuffer(buf.data(), buf.size()); - EXPECT_THAT(write(fd.get(), buf.data(), n_write), SyscallFailsWithErrno(EIO)); - - // Check the write request. - struct fuse_in_header in_header_write; - struct fuse_write_in in_payload_write; - std::vector<char> payload_buf(n_write); - auto iov_in_write = - FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf); - GetServerActualRequest(iov_in_write); - - EXPECT_EQ(in_payload_write.fh, test_fh_); - EXPECT_EQ(in_header_write.len, - sizeof(in_header_write) + sizeof(in_payload_write)); - EXPECT_EQ(in_header_write.opcode, FUSE_WRITE); - EXPECT_EQ(in_payload_write.offset, 0); - EXPECT_EQ(in_payload_write.size, n_write); - EXPECT_EQ(buf, payload_buf); -} - -TEST_F(WriteTest, WriteZero) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_)); - - // Issue the write. - std::vector<char> buf(0); - EXPECT_THAT(write(fd.get(), buf.data(), 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(WriteTest, PWrite) { - const int file_size = 512; - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, file_size)); - - // Prepare for the write. - const int n_write = 10; - struct fuse_out_header out_header_write = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out), - }; - struct fuse_write_out out_payload_write = { - .size = n_write, - }; - auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write); - SetServerResponse(FUSE_WRITE, iov_out_write); - - // Issue the write. - std::vector<char> buf(n_write); - RandomizeBuffer(buf.data(), buf.size()); - const int offset_write = file_size >> 1; - EXPECT_THAT(pwrite(fd.get(), buf.data(), n_write, offset_write), - SyscallSucceedsWithValue(n_write)); - - // Check the write request. - struct fuse_in_header in_header_write; - struct fuse_write_in in_payload_write; - std::vector<char> payload_buf(n_write); - auto iov_in_write = - FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf); - GetServerActualRequest(iov_in_write); - - EXPECT_EQ(in_payload_write.fh, test_fh_); - EXPECT_EQ(in_header_write.len, - sizeof(in_header_write) + sizeof(in_payload_write)); - EXPECT_EQ(in_header_write.opcode, FUSE_WRITE); - EXPECT_EQ(in_payload_write.offset, offset_write); - EXPECT_EQ(in_payload_write.size, n_write); - EXPECT_EQ(buf, payload_buf); -} - -TEST_F(WriteTestSmallMaxWrite, WriteSmallMaxWrie) { - const int n_fragment = 10; - const int n_write = size_fragment * n_fragment; - - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_write)); - - // Prepare for the write. - struct fuse_out_header out_header_write = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out), - }; - struct fuse_write_out out_payload_write = { - .size = size_fragment, - }; - auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write); - - for (int i = 0; i < n_fragment; ++i) { - SetServerResponse(FUSE_WRITE, iov_out_write); - } - - // Issue the write. - std::vector<char> buf(n_write); - RandomizeBuffer(buf.data(), buf.size()); - EXPECT_THAT(write(fd.get(), buf.data(), n_write), - SyscallSucceedsWithValue(n_write)); - - ASSERT_EQ(GetServerNumUnsentResponses(), 0); - ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment); - - // Check the write request. - struct fuse_in_header in_header_write; - struct fuse_write_in in_payload_write; - std::vector<char> payload_buf(size_fragment); - auto iov_in_write = - FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf); - - for (int i = 0; i < n_fragment; ++i) { - GetServerActualRequest(iov_in_write); - - EXPECT_EQ(in_payload_write.fh, test_fh_); - EXPECT_EQ(in_header_write.len, - sizeof(in_header_write) + sizeof(in_payload_write)); - EXPECT_EQ(in_header_write.opcode, FUSE_WRITE); - EXPECT_EQ(in_payload_write.offset, i * size_fragment); - EXPECT_EQ(in_payload_write.size, size_fragment); - - auto it = buf.begin() + i * size_fragment; - EXPECT_EQ(std::vector<char>(it, it + size_fragment), payload_buf); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/image/BUILD b/test/image/BUILD deleted file mode 100644 index e749e47d4..000000000 --- a/test/image/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_test( - name = "image_test", - size = "large", - srcs = [ - "image_test.go", - ], - data = [ - "latin10k.txt", - "mysql.sql", - "ruby.rb", - "ruby.sh", - ], - library = ":image", - tags = [ - # Requires docker and runsc to be configured before the test runs. - "manual", - "local", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/test/dockerutil", - "//pkg/test/testutil", - ], -) - -go_library( - name = "image", - srcs = ["image.go"], -) diff --git a/test/image/image.go b/test/image/image.go deleted file mode 100644 index 297f1ab92..000000000 --- a/test/image/image.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package image is empty. See image_test.go for description. -package image diff --git a/test/image/image_test.go b/test/image/image_test.go deleted file mode 100644 index 968e62f63..000000000 --- a/test/image/image_test.go +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package image provides end-to-end image tests for runsc. - -// Each test calls docker commands to start up a container, and tests that it -// is behaving properly, like connecting to a port or looking at the output. -// The container is killed and deleted at the end. -// -// Setup instruction in test/README.md. -package image - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// defaultWait defines how long to wait for progress. -// -// See BUILD: This is at least a "large" test, so allow up to 1 minute for any -// given "wait" step. Note that all tests are run in parallel, which may cause -// individual slow-downs (but a huge speed-up in aggregate). -const defaultWait = time.Minute - -func TestHelloWorld(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Run the basic container. - out, err := d.Run(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "echo", "Hello world!") - if err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Check the output. - if !strings.Contains(out, "Hello world!") { - t.Fatalf("docker didn't say hello: got %s", out) - } -} - -func runHTTPRequest(ip string, port int) error { - url := fmt.Sprintf("http://%s:%d/not-found", ip, port) - resp, err := http.Get(url) - if err != nil { - return fmt.Errorf("error reaching http server: %v", err) - } - if want := http.StatusNotFound; resp.StatusCode != want { - return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - - url = fmt.Sprintf("http://%s:%d/latin10k.txt", ip, port) - resp, err = http.Get(url) - if err != nil { - return fmt.Errorf("Error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - return fmt.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("Error reading http response: %v", err) - } - defer resp.Body.Close() - - // READALL is the last word in the file. Ensures everything was read. - if want := "READALL"; strings.HasSuffix(string(body), want) { - return fmt.Errorf("response doesn't contain %q, resp: %q", want, body) - } - return nil -} - -func testHTTPServer(t *testing.T, ip string, port int) { - const requests = 10 - ch := make(chan error, requests) - for i := 0; i < requests; i++ { - go func() { - start := time.Now() - err := runHTTPRequest(ip, port) - log.Printf("Response time %v: %v", time.Since(start).String(), err) - ch <- err - }() - } - - for i := 0; i < requests; i++ { - err := <-ch - if err != nil { - t.Errorf("testHTTPServer(%s, %d) failed: %v", ip, port, err) - } - } -} - -func TestHttpd(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 80 - opts := dockerutil.RunOpts{ - Image: "basic/httpd", - Ports: []int{port}, - } - d.CopyFiles(&opts, "/usr/local/apache2/htdocs", "test/image/latin10k.txt") - if err := d.Spawn(ctx, opts); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Errorf("WaitForHTTP() timeout: %v", err) - } - - testHTTPServer(t, ip.String(), port) -} - -func TestNginx(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 80 - opts := dockerutil.RunOpts{ - Image: "basic/nginx", - Ports: []int{port}, - } - d.CopyFiles(&opts, "/usr/share/nginx/html", "test/image/latin10k.txt") - if err := d.Spawn(ctx, opts); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Errorf("WaitForHTTP() timeout: %v", err) - } - - testHTTPServer(t, ip.String(), port) -} - -func TestMysql(t *testing.T) { - ctx := context.Background() - server := dockerutil.MakeContainer(ctx, t) - defer server.CleanUp(ctx) - - // Start the container. - if err := server.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/mysql", - Env: []string{"MYSQL_ROOT_PASSWORD=foobar123"}, - }); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Wait until it's up and running. - if _, err := server.WaitForOutput(ctx, "port: 3306 MySQL Community Server", defaultWait); err != nil { - t.Fatalf("WaitForOutput() timeout: %v", err) - } - - // Generate the client and copy in the SQL payload. - client := dockerutil.MakeContainer(ctx, t) - defer client.CleanUp(ctx) - - // Tell mysql client to connect to the server and execute the file in - // verbose mode to verify the output. - opts := dockerutil.RunOpts{ - Image: "basic/mysql", - Links: []string{server.MakeLink("mysql")}, - } - client.CopyFiles(&opts, "/sql", "test/image/mysql.sql") - if _, err := client.Run(ctx, opts, "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Ensure file executed to the end and shutdown mysql. - if _, err := server.WaitForOutput(ctx, "mysqld: Shutdown complete", defaultWait); err != nil { - t.Fatalf("WaitForOutput() timeout: %v", err) - } -} - -func TestTomcat(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the server. - port := 8080 - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/tomcat", - Ports: []int{port}, - }); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Ensure that content is being served. - url := fmt.Sprintf("http://%s:%d", ip.String(), port) - resp, err := http.Get(url) - if err != nil { - t.Errorf("Error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - t.Errorf("Wrong response code, got: %d, want: %d", resp.StatusCode, want) - } -} - -func TestRuby(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Execute the ruby workload. - port := 8080 - opts := dockerutil.RunOpts{ - Image: "basic/ruby", - Ports: []int{port}, - } - d.CopyFiles(&opts, "/src", "test/image/ruby.rb", "test/image/ruby.sh") - if err := d.Spawn(ctx, opts, "/src/ruby.sh"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running, 'gem install' can take some time. - if err := testutil.WaitForHTTP(ip.String(), port, time.Minute); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Ensure that content is being served. - url := fmt.Sprintf("http://%s:%d", ip.String(), port) - resp, err := http.Get(url) - if err != nil { - t.Errorf("error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - t.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("error reading body: %v", err) - } - if got, want := string(body), "Hello World"; !strings.Contains(got, want) { - t.Errorf("invalid body content, got: %q, want: %q", got, want) - } -} - -func TestStdio(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - wantStdout := "hello stdout" - wantStderr := "bonjour stderr" - cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr) - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "/bin/sh", "-c", cmd); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - for _, want := range []string{wantStdout, wantStderr} { - if _, err := d.WaitForOutput(ctx, want, defaultWait); err != nil { - t.Fatalf("docker didn't get output %q : %v", want, err) - } - } -} - -func TestMain(m *testing.M) { - dockerutil.EnsureSupportedDockerVersion() - flag.Parse() - os.Exit(m.Run()) -} diff --git a/test/image/latin10k.txt b/test/image/latin10k.txt deleted file mode 100644 index 61341e00b..000000000 --- a/test/image/latin10k.txt +++ /dev/null @@ -1,33 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ut placerat felis. Maecenas urna est, auctor a efficitur sit amet, egestas et augue. Curabitur dignissim scelerisque nunc vel cursus. Ut vehicula est pretium, consectetur nunc non, pharetra ligula. Curabitur ut ultricies metus. Suspendisse pulvinar, orci sed fermentum vestibulum, eros turpis molestie lectus, nec elementum risus dolor mattis felis. Donec ultrices ipsum sem, at pretium lacus convallis at. Mauris nulla enim, tincidunt non bibendum at, vehicula pulvinar mauris. - -Duis in dapibus turpis. Pellentesque maximus magna odio, ac congue libero laoreet quis. Maecenas euismod risus in justo aliquam accumsan. Nunc quis ornare arcu, sit amet sodales elit. Phasellus nec scelerisque nisl, a tincidunt arcu. Proin ornare est nunc, sed suscipit orci interdum et. Suspendisse condimentum venenatis diam in tempor. Aliquam egestas lectus in rutrum tempus. Donec id egestas eros. Donec molestie consequat purus, sed posuere odio venenatis vitae. Nunc placerat augue id vehicula varius. In hac habitasse platea dictumst. Proin at est accumsan, venenatis quam a, fermentum risus. Phasellus posuere pellentesque enim, id suscipit magna consequat ut. Quisque ut tortor ante. - -Cras ut vulputate metus, a laoreet lectus. Vivamus ultrices molestie odio in tristique. Morbi faucibus mi eget sollicitudin fringilla. Fusce vitae lacinia ligula. Sed egestas sed diam eu posuere. Maecenas justo nisl, venenatis vel nibh vel, cursus aliquam velit. Praesent lacinia dui id erat venenatis rhoncus. Morbi gravida felis ante, sit amet vehicula orci rhoncus vitae. - -Sed finibus sagittis dictum. Proin auctor suscipit sem et mattis. Phasellus libero ligula, pellentesque ut felis porttitor, fermentum sollicitudin orci. Nulla eu nulla nibh. Fusce a eros risus. Proin vel magna risus. Donec nec elit eleifend, scelerisque sapien vitae, pharetra quam. Donec porttitor mauris scelerisque, tempus orci hendrerit, dapibus felis. Nullam libero elit, sollicitudin a aliquam at, ultrices in erat. Mauris eget ligula sodales, porta turpis et, scelerisque odio. Mauris mollis leo vitae purus gravida, in tempor nunc efficitur. Nulla facilisis posuere augue, nec pellentesque lectus eleifend ac. Vestibulum convallis est a feugiat tincidunt. Donec vitae enim volutpat, tincidunt eros eu, malesuada nibh. - -Quisque molestie, magna ornare elementum convallis, erat enim sagittis ipsum, eget porttitor sapien arcu id purus. Donec ut cursus diam. Nulla rutrum nulla et mi fermentum, vel tempus tellus posuere. Proin vitae pharetra nulla, nec ornare ex. Nulla consequat, augue a accumsan euismod, turpis leo ornare ligula, a pulvinar enim dolor ut augue. Quisque volutpat, lectus a varius mollis, nisl eros feugiat sem, at egestas lacus justo eu elit. Vestibulum scelerisque mauris est, sagittis interdum nunc accumsan sit amet. Maecenas aliquet ex ut lacus ornare, eu sagittis nibh imperdiet. Duis ultrices nisi velit, sed sodales risus sollicitudin et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam a accumsan augue, vitae pulvinar nulla. Pellentesque euismod sodales magna, nec luctus eros mattis eget. Sed lacinia suscipit lectus, eget consectetur dui pellentesque sed. Nullam nec mattis tellus. - -Aliquam erat volutpat. Praesent lobortis massa porttitor eros tincidunt, nec consequat diam pharetra. Duis efficitur non lorem sed mattis. Suspendisse justo nunc, pulvinar eu porttitor at, facilisis id eros. Suspendisse potenti. Cras molestie aliquet orci ut fermentum. In tempus aliquet eros nec suscipit. Suspendisse in mauris ut lectus ultrices blandit sit amet vitae est. Nam magna massa, porttitor ut semper id, feugiat vel quam. Suspendisse dignissim posuere scelerisque. Donec scelerisque lorem efficitur suscipit suscipit. Nunc luctus ligula et scelerisque lacinia. - -Suspendisse potenti. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed ultrices, sem in venenatis scelerisque, tellus ipsum porttitor urna, et iaculis lectus odio ac nisi. Integer luctus dui urna, at sollicitudin elit dapibus eu. Praesent nibh ante, porttitor a ante in, ullamcorper pretium felis. Aliquam vel tortor imperdiet, imperdiet lorem et, cursus mi. Proin tempus velit est, ut hendrerit metus gravida sed. Sed nibh sapien, faucibus quis ipsum in, scelerisque lacinia elit. In nec magna eu magna laoreet rhoncus. Donec vitae rutrum mauris. Integer urna felis, consequat at rhoncus vitae, auctor quis elit. Duis a pulvinar sem, nec gravida nisl. Nam non dapibus purus. Praesent vestibulum turpis nec erat porttitor, a scelerisque purus tincidunt. - -Nam fringilla leo nisi, nec placerat nisl luctus eget. Aenean malesuada nunc porta sapien sodales convallis. Suspendisse ut massa tempor, ullamcorper mi ut, faucibus turpis. Vivamus at sagittis metus. Donec varius ac mi eget sodales. Nulla feugiat, nulla eu fringilla fringilla, nunc lorem sollicitudin quam, vitae lacinia velit lorem eu orci. Mauris leo urna, pellentesque ac posuere non, pellentesque sit amet quam. - -Vestibulum porta diam urna, a aliquet nibh vestibulum et. Proin interdum bibendum nisl sed rhoncus. Sed vel diam hendrerit, faucibus ante et, hendrerit diam. Nunc dolor augue, mattis non dolor vel, luctus sodales neque. Cras malesuada fermentum dolor eu lobortis. Integer dapibus volutpat consequat. Maecenas posuere feugiat nunc. Donec vel mollis elit, volutpat consequat enim. Nulla id nisi finibus orci imperdiet elementum. Phasellus ultrices, elit vitae consequat rutrum, nisl est congue massa, quis condimentum justo nisi vitae turpis. Maecenas aliquet risus sit amet accumsan elementum. Proin non finibus elit, sit amet lobortis augue. - -Morbi pretium pulvinar sem vel sollicitudin. Proin imperdiet fringilla leo, non pellentesque lacus gravida nec. Vivamus ullamcorper consectetur ligula eu consectetur. Curabitur sit amet tempus purus. Curabitur quam quam, tincidunt eu tempus vel, volutpat at ipsum. Maecenas lobortis elit ac justo interdum, sit amet mattis ligula mollis. Sed posuere ligula et felis convallis tempor. Aliquam nec mollis velit. Donec varius sit amet erat at imperdiet. Nulla ipsum justo, tempor non sollicitudin gravida, dignissim vel orci. In hac habitasse platea dictumst. Cras cursus tellus id arcu aliquet accumsan. Phasellus ac erat dui. - -Duis mollis metus at mi luctus aliquam. Duis varius eget erat ac porttitor. Phasellus lobortis sagittis lacinia. Etiam sagittis eget erat in pulvinar. Phasellus sodales risus nec vulputate accumsan. Cras sit amet pellentesque dui. Praesent consequat felis mi, at vulputate diam convallis a. Donec hendrerit nibh vel justo consequat dictum. In euismod, dui sit amet malesuada suscipit, mauris ex rhoncus eros, sed ornare arcu nunc eu urna. Pellentesque eget erat augue. Integer rutrum mauris sem, nec sodales nulla cursus vel. Vivamus porta, urna vel varius vulputate, nulla arcu malesuada dui, a ultrices magna ante sed nibh. - -Morbi ultricies aliquam lorem id bibendum. Donec sit amet nunc vitae massa gravida eleifend hendrerit vel libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla vestibulum tempus condimentum. Aliquam dolor ipsum, condimentum in sapien et, tempor iaculis nulla. Aenean non pharetra augue. Maecenas mattis dignissim maximus. Fusce elementum tincidunt massa sit amet lobortis. Phasellus nec pharetra dui, et malesuada ante. Nullam commodo pretium tellus. Praesent sollicitudin, enim eget imperdiet scelerisque, odio felis vulputate dolor, eget auctor neque tellus ac lorem. - -In consectetur augue et sapien feugiat varius. Nam tortor mi, consectetur ac felis non, elementum venenatis augue. Suspendisse ut tellus in est sagittis cursus. Quisque faucibus, neque sit amet semper congue, nibh augue finibus odio, vitae interdum dolor arcu eget arcu. Curabitur dictum risus massa, non tincidunt urna molestie non. Maecenas eu quam purus. Donec vulputate, dui eu accumsan blandit, mauris tortor tristique mi, sed blandit leo quam id quam. Ut venenatis sagittis malesuada. Integer non auctor orci. Duis consectetur massa felis. Fusce euismod est sit amet bibendum finibus. Vestibulum dolor ex, tempor at elit in, iaculis cursus dui. Nunc sed neque ac risus rutrum tempus sit amet at ante. In hac habitasse platea dictumst. - -Donec rutrum, velit nec viverra tincidunt, est velit viverra neque, quis auctor leo ex at lectus. Morbi eget purus nisi. Aliquam lacus dui, interdum vitae elit at, venenatis dignissim est. Duis ac mollis lorem. Vivamus a vestibulum quam. Maecenas non metus dolor. Praesent tortor nunc, tristique at nisl molestie, vulputate eleifend diam. Integer ultrices lacus odio, vel imperdiet enim accumsan id. Sed ligula tortor, interdum eu velit eget, pharetra pulvinar magna. Sed non lacus in eros tincidunt sagittis ac vel justo. Donec vitae leo sagittis, accumsan ante sit amet, accumsan odio. Ut volutpat ultricies tortor. Vestibulum tempus purus et est tristique sagittis quis vitae turpis. - -Nam iaculis neque lacus, eget euismod turpis blandit eget. In hac habitasse platea dictumst. Phasellus justo neque, scelerisque sit amet risus ut, pretium commodo nisl. Phasellus auctor sapien sed ex bibendum fermentum. Proin maximus odio a ante ornare, a feugiat lorem egestas. Etiam efficitur tortor a ante tincidunt interdum. Nullam non est ac massa congue efficitur sit amet nec eros. Nullam at ipsum vel mauris tincidunt efficitur. Duis pulvinar nisl elit, id auctor risus laoreet ac. Sed nunc mauris, tristique id leo ut, condimentum congue nunc. Sed ultricies, mauris et convallis faucibus, justo ex faucibus est, at lobortis purus justo non arcu. Integer vel facilisis elit, dapibus imperdiet mauris. - -Pellentesque non mattis turpis, eget bibendum velit. Fusce sollicitudin ante ac tincidunt rhoncus. Praesent porta scelerisque consequat. Donec eleifend faucibus sollicitudin. Quisque vitae purus eget tortor tempor ultrices. Maecenas mauris diam, semper vitae est non, imperdiet tempor magna. Duis elit lacus, auctor vestibulum enim eget, rhoncus porttitor tortor. - -Donec non rhoncus nibh. Cras dapibus justo vitae nunc accumsan, id congue erat egestas. Aenean at ante ante. Duis eleifend imperdiet dREADALL diff --git a/test/image/mysql.sql b/test/image/mysql.sql deleted file mode 100644 index 51554b98d..000000000 --- a/test/image/mysql.sql +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -SHOW databases; -USE mysql; - -CREATE TABLE foo (id int); -INSERT INTO foo VALUES(1); -SELECT * FROM foo; -DROP TABLE foo; - -shutdown; diff --git a/test/image/ruby.rb b/test/image/ruby.rb deleted file mode 100644 index aced49c6d..000000000 --- a/test/image/ruby.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'sinatra' - -set :bind, "0.0.0.0" -set :port, 8080 - -get '/' do - 'Hello World' -end - diff --git a/test/image/ruby.sh b/test/image/ruby.sh deleted file mode 100755 index ebe8d5b0e..000000000 --- a/test/image/ruby.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e - -gem install sinatra -ruby /src/ruby.rb diff --git a/test/iptables/BUILD b/test/iptables/BUILD deleted file mode 100644 index 94d4ca2d4..000000000 --- a/test/iptables/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "iptables", - testonly = 1, - srcs = [ - "filter_input.go", - "filter_output.go", - "iptables.go", - "iptables_unsafe.go", - "iptables_util.go", - "nat.go", - ], - visibility = ["//test/iptables:__subpackages__"], - deps = [ - "//pkg/binary", - "//pkg/test/testutil", - "//pkg/usermem", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "iptables_test", - size = "large", - srcs = [ - "iptables_test.go", - ], - data = ["//test/iptables/runner"], - library = ":iptables", - tags = [ - "local", - "manual", - ], - deps = [ - "//pkg/test/dockerutil", - "//pkg/test/testutil", - ], -) diff --git a/test/iptables/README.md b/test/iptables/README.md deleted file mode 100644 index 1196f8eb5..000000000 --- a/test/iptables/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# iptables Tests - -iptables tests are run via `make iptables-tests`. - -iptables require some extra Docker configuration to work. Enable IPv6 in -`/etc/docker/daemon.json` (make sure to restart Docker if you change this file): - -```json -{ - "experimental": true, - "fixed-cidr-v6": "2001:db8:1::/64", - "ipv6": true, - // Runtimes and other Docker config... -} -``` - -And if you're running manually (i.e. not using the `make` target), you'll need -to: - -* Enable iptables via `modprobe iptables_filter && modprobe ip6table_filter`. -* Enable `--net-raw` in your chosen runtime in `/etc/docker/daemon.json` (make - sure to restart Docker if you change this file). - -The resulting runtime should look something like this: - -```json -"runsc": { - "path": "/tmp/iptables/runsc", - "runtimeArgs": [ - "--debug-log", - "/tmp/iptables/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%", - "--net-raw" - ] -}, -// ... -``` - -## Test Structure - -Each test implements `TestCase`, providing (1) a function to run inside the -container and (2) a function to run locally. Those processes are given each -others' IP addresses. The test succeeds when both functions succeed. - -The function inside the container (`ContainerAction`) typically sets some -iptables rules and then tries to send or receive packets. The local function -(`LocalAction`) will typically just send or receive packets. - -### Adding Tests - -1) Add your test to the `iptables` package. - -2) Register the test in an `init` function via `RegisterTestCase` (see -`filter_input.go` as an example). - -3) Add it to `iptables_test.go` (see the other tests in that file). - -Your test is now runnable with bazel! - -## Run individual tests - -Build and install `runsc`. Re-run this when you modify gVisor: - -```bash -$ bazel build //runsc && sudo cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc $(which runsc) -``` - -Build the testing Docker container. Re-run this when you modify the test code in -this directory: - -```bash -$ make load-iptables -``` - -Run an individual test via: - -```bash -$ bazel test //test/iptables:iptables_test --test_filter=<TESTNAME> -``` - -To run an individual test with `runc`: - -```bash -$ bazel test //test/iptables:iptables_test --test_filter=<TESTNAME> --test_arg=--runtime=runc -``` diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go deleted file mode 100644 index 4739bc06f..000000000 --- a/test/iptables/filter_input.go +++ /dev/null @@ -1,990 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" - "time" -) - -const ( - dropPort = 2401 - acceptPort = 2402 - sendloopDuration = 2 * time.Second - chainName = "foochain" -) - -func init() { - RegisterTestCase(&FilterInputDropAll{}) - RegisterTestCase(&FilterInputDropDifferentUDPPort{}) - RegisterTestCase(&FilterInputDropOnlyUDP{}) - RegisterTestCase(&FilterInputDropTCPDestPort{}) - RegisterTestCase(&FilterInputDropTCPSrcPort{}) - RegisterTestCase(&FilterInputDropUDPPort{}) - RegisterTestCase(&FilterInputDropUDP{}) - RegisterTestCase(&FilterInputCreateUserChain{}) - RegisterTestCase(&FilterInputDefaultPolicyAccept{}) - RegisterTestCase(&FilterInputDefaultPolicyDrop{}) - RegisterTestCase(&FilterInputReturnUnderflow{}) - RegisterTestCase(&FilterInputSerializeJump{}) - RegisterTestCase(&FilterInputJumpBasic{}) - RegisterTestCase(&FilterInputJumpReturn{}) - RegisterTestCase(&FilterInputJumpReturnDrop{}) - RegisterTestCase(&FilterInputJumpBuiltin{}) - RegisterTestCase(&FilterInputJumpTwice{}) - RegisterTestCase(&FilterInputDestination{}) - RegisterTestCase(&FilterInputInvertDestination{}) - RegisterTestCase(&FilterInputSource{}) - RegisterTestCase(&FilterInputInvertSource{}) - RegisterTestCase(&FilterInputInterfaceAccept{}) - RegisterTestCase(&FilterInputInterfaceDrop{}) - RegisterTestCase(&FilterInputInterface{}) - RegisterTestCase(&FilterInputInterfaceBeginsWith{}) - RegisterTestCase(&FilterInputInterfaceInvertDrop{}) - RegisterTestCase(&FilterInputInterfaceInvertAccept{}) -} - -// FilterInputDropUDP tests that we can drop UDP traffic. -type FilterInputDropUDP struct{ containerCase } - -var _ TestCase = (*FilterInputDropUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropUDP) Name() string { - return "FilterInputDropUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputDropOnlyUDP tests that "-p udp -j DROP" only affects UDP traffic. -type FilterInputDropOnlyUDP struct{ baseCase } - -var _ TestCase = (*FilterInputDropOnlyUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropOnlyUDP) Name() string { - return "FilterInputDropOnlyUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropOnlyUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { - return err - } - - // Listen for a TCP connection, which should be allowed. - if err := listenTCP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("failed to establish a connection %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropOnlyUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Try to establish a TCP connection with the container, which should - // succeed. - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDropUDPPort tests that we can drop UDP traffic by port. -type FilterInputDropUDPPort struct{ containerCase } - -var _ TestCase = (*FilterInputDropUDPPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropUDPPort) Name() string { - return "FilterInputDropUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputDropDifferentUDPPort tests that dropping traffic for a single UDP port -// doesn't drop packets on other ports. -type FilterInputDropDifferentUDPPort struct{ containerCase } - -var _ TestCase = (*FilterInputDropDifferentUDPPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropDifferentUDPPort) Name() string { - return "FilterInputDropDifferentUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropDifferentUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on another port. - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropDifferentUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDropTCPDestPort tests that connections are not accepted on specified source ports. -type FilterInputDropTCPDestPort struct{ baseCase } - -var _ TestCase = (*FilterInputDropTCPDestPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropTCPDestPort) Name() string { - return "FilterInputDropTCPDestPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on drop port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Ensure we cannot connect to the container. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("expected not to connect, but was able to connect on port %d", dropPort) - } - return nil -} - -// FilterInputDropTCPSrcPort tests that connections are not accepted on specified source ports. -type FilterInputDropTCPSrcPort struct{ baseCase } - -var _ TestCase = (*FilterInputDropTCPSrcPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropTCPSrcPort) Name() string { - return "FilterInputDropTCPSrcPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Drop anything from an ephemeral port. - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--sport", "1024:65535", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but was", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Ensure we cannot connect to the container. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("expected not to connect, but was able to connect on port %d", acceptPort) - } - return nil -} - -// FilterInputDropAll tests that we can drop all traffic to the INPUT chain. -type FilterInputDropAll struct{ containerCase } - -var _ TestCase = (*FilterInputDropAll)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropAll) Name() string { - return "FilterInputDropAll" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-j", "DROP"); err != nil { - return err - } - - // Listen for all packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets should have been dropped, but got a packet") - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputMultiUDPRules verifies that multiple UDP rules are applied -// correctly. This has the added benefit of testing whether we're serializing -// rules correctly -- if we do it incorrectly, the iptables tool will -// misunderstand and save the wrong tables. -type FilterInputMultiUDPRules struct{ baseCase } - -var _ TestCase = (*FilterInputMultiUDPRules)(nil) - -// Name implements TestCase.Name. -func (*FilterInputMultiUDPRules) Name() string { - return "FilterInputMultiUDPRules" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputMultiUDPRules) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"}, - {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"}, - {"-L"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputMultiUDPRules) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputRequireProtocolUDP checks that "-m udp" requires "-p udp" to be -// specified. -type FilterInputRequireProtocolUDP struct{ baseCase } - -var _ TestCase = (*FilterInputRequireProtocolUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputRequireProtocolUDP) Name() string { - return "FilterInputRequireProtocolUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputRequireProtocolUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err == nil { - return errors.New("expected iptables to fail with out \"-p udp\", but succeeded") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputRequireProtocolUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputCreateUserChain tests chain creation. -type FilterInputCreateUserChain struct{ baseCase } - -var _ TestCase = (*FilterInputCreateUserChain)(nil) - -// Name implements TestCase.Name. -func (*FilterInputCreateUserChain) Name() string { - return "FilterInputCreateUserChain" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputCreateUserChain) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - // Create a chain. - {"-N", chainName}, - // Add a simple rule to the chain. - {"-A", chainName, "-j", "DROP"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputCreateUserChain) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputDefaultPolicyAccept tests the default ACCEPT policy. -type FilterInputDefaultPolicyAccept struct{ containerCase } - -var _ TestCase = (*FilterInputDefaultPolicyAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDefaultPolicyAccept) Name() string { - return "FilterInputDefaultPolicyAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDefaultPolicyAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Set the default policy to accept, then receive a packet. - if err := filterTable(ipv6, "-P", "INPUT", "ACCEPT"); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDefaultPolicyAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDefaultPolicyDrop tests the default DROP policy. -type FilterInputDefaultPolicyDrop struct{ containerCase } - -var _ TestCase = (*FilterInputDefaultPolicyDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDefaultPolicyDrop) Name() string { - return "FilterInputDefaultPolicyDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDefaultPolicyDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-P", "INPUT", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDefaultPolicyDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputReturnUnderflow tests that -j RETURN in a built-in chain causes -// the underflow rule (i.e. default policy) to be executed. -type FilterInputReturnUnderflow struct{ containerCase } - -var _ TestCase = (*FilterInputReturnUnderflow)(nil) - -// Name implements TestCase.Name. -func (*FilterInputReturnUnderflow) Name() string { - return "FilterInputReturnUnderflow" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputReturnUnderflow) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Add a RETURN rule followed by an unconditional accept, and set the - // default policy to DROP. - rules := [][]string{ - {"-A", "INPUT", "-j", "RETURN"}, - {"-A", "INPUT", "-j", "DROP"}, - {"-P", "INPUT", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // We should receive packets, as the RETURN rule will trigger the default - // ACCEPT policy. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputReturnUnderflow) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputSerializeJump verifies that we can serialize jumps. -type FilterInputSerializeJump struct{ baseCase } - -var _ TestCase = (*FilterInputSerializeJump)(nil) - -// Name implements TestCase.Name. -func (*FilterInputSerializeJump) Name() string { - return "FilterInputSerializeJump" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputSerializeJump) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Write a JUMP rule, the serialize it with `-L`. - rules := [][]string{ - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-L"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputSerializeJump) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputJumpBasic jumps to a chain and executes a rule there. -type FilterInputJumpBasic struct{ containerCase } - -var _ TestCase = (*FilterInputJumpBasic)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpBasic) Name() string { - return "FilterInputJumpBasic" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpBasic) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpBasic) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputJumpReturn jumps, returns, and executes a rule. -type FilterInputJumpReturn struct{ containerCase } - -var _ TestCase = (*FilterInputJumpReturn)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpReturn) Name() string { - return "FilterInputJumpReturn" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpReturn) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-N", chainName}, - {"-P", "INPUT", "ACCEPT"}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", "RETURN"}, - {"-A", chainName, "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpReturn) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets. -type FilterInputJumpReturnDrop struct{ containerCase } - -var _ TestCase = (*FilterInputJumpReturnDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpReturnDrop) Name() string { - return "FilterInputJumpReturnDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpReturnDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-A", "INPUT", "-j", "DROP"}, - {"-A", chainName, "-j", "RETURN"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpReturnDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal. -type FilterInputJumpBuiltin struct{ baseCase } - -var _ TestCase = (*FilterInputJumpBuiltin)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpBuiltin) Name() string { - return "FilterInputJumpBuiltin" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpBuiltin) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-j", "OUTPUT"); err == nil { - return fmt.Errorf("iptables should be unable to jump to a built-in chain") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpBuiltin) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputJumpTwice jumps twice, then returns twice and executes a rule. -type FilterInputJumpTwice struct{ containerCase } - -var _ TestCase = (*FilterInputJumpTwice)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpTwice) Name() string { - return "FilterInputJumpTwice" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpTwice) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - const chainName2 = chainName + "2" - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-N", chainName}, - {"-N", chainName2}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", chainName2}, - {"-A", "INPUT", "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // UDP packets should jump and return twice, eventually hitting the - // ACCEPT rule. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpTwice) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDestination verifies that we can filter packets via `-d -// <ipaddr>`. -type FilterInputDestination struct{ containerCase } - -var _ TestCase = (*FilterInputDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDestination) Name() string { - return "FilterInputDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - addrs, err := localAddrs(ipv6) - if err != nil { - return err - } - - // Make INPUT's default action DROP, then ACCEPT all packets bound for - // this machine. - rules := [][]string{{"-P", "INPUT", "DROP"}} - for _, addr := range addrs { - rules = append(rules, []string{"-A", "INPUT", "-d", addr, "-j", "ACCEPT"}) - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInvertDestination verifies that we can filter packets via `! -d -// <ipaddr>`. -type FilterInputInvertDestination struct{ containerCase } - -var _ TestCase = (*FilterInputInvertDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInvertDestination) Name() string { - return "FilterInputInvertDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets not bound - // for 127.0.0.1. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputSource verifies that we can filter packets via `-s -// <ipaddr>`. -type FilterInputSource struct{ containerCase } - -var _ TestCase = (*FilterInputSource)(nil) - -// Name implements TestCase.Name. -func (*FilterInputSource) Name() string { - return "FilterInputSource" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets from this - // machine. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "-s", fmt.Sprintf("%v", ip), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInvertSource verifies that we can filter packets via `! -s -// <ipaddr>`. -type FilterInputInvertSource struct{ containerCase } - -var _ TestCase = (*FilterInputInvertSource)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInvertSource) Name() string { - return "FilterInputInvertSource" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInvertSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets not bound - // for 127.0.0.1. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "!", "-s", localIP(ipv6), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInvertSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceAccept tests that packets are accepted from interface -// matching the iptables rule. -type FilterInputInterfaceAccept struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceAccept) Name() string { - return "FilterInputInterfaceAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "ACCEPT"); err != nil { - return err - } - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceDrop tests that packets are dropped from interface -// matching the iptables rule. -type FilterInputInterfaceDrop struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceDrop) Name() string { - return "FilterInputInterfaceDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("packets should have been dropped, but got a packet") -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterface tests that packets are not dropped from interface which -// is not matching the interface name in the iptables rule. -type FilterInputInterface struct{ localCase } - -var _ TestCase = (*FilterInputInterface)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterface) Name() string { - return "FilterInputInterface" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "lo", "-j", "DROP"); err != nil { - return err - } - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceBeginsWith tests that packets are dropped from an -// interface which begins with the given interface name. -type FilterInputInterfaceBeginsWith struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceBeginsWith)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceBeginsWith) Name() string { - return "FilterInputInterfaceBeginsWith" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "e+", "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("packets should have been dropped, but got a packet") -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceInvertDrop tests that we selectively drop packets from -// interface not matching the interface name. -type FilterInputInterfaceInvertDrop struct{ baseCase } - -var _ TestCase = (*FilterInputInterfaceInvertDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceInvertDrop) Name() string { - return "FilterInputInterfaceInvertDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("connection on port %d should not be accepted, but was accepted", acceptPort) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err != nil { - var operr *net.OpError - if errors.As(err, &operr) && operr.Timeout() { - return nil - } - return fmt.Errorf("error connecting: %w", err) - } - return fmt.Errorf("connection destined to port %d should not be accepted, but was accepted", acceptPort) -} - -// FilterInputInterfaceInvertAccept tests that we can selectively accept packets -// not matching the specific incoming interface. -type FilterInputInterfaceInvertAccept struct{ baseCase } - -var _ TestCase = (*FilterInputInterfaceInvertAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceInvertAccept) Name() string { - return "FilterInputInterfaceInvertAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "ACCEPT"); err != nil { - return err - } - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go deleted file mode 100644 index bcb2a3b70..000000000 --- a/test/iptables/filter_output.go +++ /dev/null @@ -1,714 +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 iptables - -import ( - "context" - "errors" - "fmt" - "net" -) - -func init() { - RegisterTestCase(&FilterOutputDropTCPDestPort{}) - RegisterTestCase(&FilterOutputDropTCPSrcPort{}) - RegisterTestCase(&FilterOutputDestination{}) - RegisterTestCase(&FilterOutputInvertDestination{}) - RegisterTestCase(&FilterOutputAcceptTCPOwner{}) - RegisterTestCase(&FilterOutputDropTCPOwner{}) - RegisterTestCase(&FilterOutputAcceptUDPOwner{}) - RegisterTestCase(&FilterOutputDropUDPOwner{}) - RegisterTestCase(&FilterOutputOwnerFail{}) - RegisterTestCase(&FilterOutputAcceptGIDOwner{}) - RegisterTestCase(&FilterOutputDropGIDOwner{}) - RegisterTestCase(&FilterOutputInvertGIDOwner{}) - RegisterTestCase(&FilterOutputInvertUIDOwner{}) - RegisterTestCase(&FilterOutputInvertUIDAndGIDOwner{}) - RegisterTestCase(&FilterOutputInterfaceAccept{}) - RegisterTestCase(&FilterOutputInterfaceDrop{}) - RegisterTestCase(&FilterOutputInterface{}) - RegisterTestCase(&FilterOutputInterfaceBeginsWith{}) - RegisterTestCase(&FilterOutputInterfaceInvertDrop{}) - RegisterTestCase(&FilterOutputInterfaceInvertAccept{}) -} - -// FilterOutputDropTCPDestPort tests that connections are not accepted on -// specified source ports. -type FilterOutputDropTCPDestPort struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPDestPort)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPDestPort) Name() string { - return "FilterOutputDropTCPDestPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } - - return nil -} - -// FilterOutputDropTCPSrcPort tests that connections are not accepted on -// specified source ports. -type FilterOutputDropTCPSrcPort struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPSrcPort)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPSrcPort) Name() string { - return "FilterOutputDropTCPSrcPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--sport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on drop port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) - } - - return nil -} - -// FilterOutputAcceptTCPOwner tests that TCP connections from uid owner are accepted. -type FilterOutputAcceptTCPOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputAcceptTCPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptTCPOwner) Name() string { - return "FilterOutputAcceptTCPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputDropTCPOwner tests that TCP connections from uid owner are dropped. -type FilterOutputDropTCPOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPOwner) Name() string { - return "FilterOutputDropTCPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should be dropped, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should be dropped, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputAcceptUDPOwner tests that UDP packets from uid owner are accepted. -type FilterOutputAcceptUDPOwner struct{ localCase } - -var _ TestCase = (*FilterOutputAcceptUDPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptUDPOwner) Name() string { - return "FilterOutputAcceptUDPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Send UDP packets on acceptPort. - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputDropUDPOwner tests that UDP packets from uid owner are dropped. -type FilterOutputDropUDPOwner struct{ localCase } - -var _ TestCase = (*FilterOutputDropUDPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropUDPOwner) Name() string { - return "FilterOutputDropUDPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Send UDP packets on dropPort. - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received") - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputOwnerFail tests that without uid/gid option, owner rule -// will fail. -type FilterOutputOwnerFail struct{ baseCase } - -var _ TestCase = (*FilterOutputOwnerFail)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputOwnerFail) Name() string { - return "FilterOutputOwnerFail" -} - -// 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 nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputOwnerFail) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // no-op. - return nil -} - -// FilterOutputAcceptGIDOwner tests that TCP connections from gid owner are accepted. -type FilterOutputAcceptGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputAcceptGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptGIDOwner) Name() string { - return "FilterOutputAcceptGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputDropGIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputDropGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputDropGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropGIDOwner) Name() string { - return "FilterOutputDropGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInvertGIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputInvertGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertGIDOwner) Name() string { - return "FilterOutputInvertGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--gid-owner", "root", "-j", "ACCEPT"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInvertUIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputInvertUIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertUIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertUIDOwner) Name() string { - return "FilterOutputInvertUIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertUIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "-j", "DROP"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertUIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputInvertUIDAndGIDOwner tests that TCP connections from uid and gid -// owner are dropped. -type FilterOutputInvertUIDAndGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertUIDAndGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertUIDAndGIDOwner) Name() string { - return "FilterOutputInvertUIDAndGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertUIDAndGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "!", "--gid-owner", "root", "-j", "ACCEPT"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertUIDAndGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputDestination tests that we can selectively allow packets to -// certain destinations. -type FilterOutputDestination struct{ localCase } - -var _ TestCase = (*FilterOutputDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDestination) Name() string { - return "FilterOutputDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - var rules [][]string - if ipv6 { - rules = [][]string{ - {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, - // Allow solicited node multicast addresses so we can send neighbor - // solicitations. - {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - } else { - rules = [][]string{ - {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInvertDestination tests that we can selectively allow packets -// not headed for a particular destination. -type FilterOutputInvertDestination struct{ localCase } - -var _ TestCase = (*FilterOutputInvertDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertDestination) Name() string { - return "FilterOutputInvertDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceAccept tests that packets are sent via interface -// matching the iptables rule. -type FilterOutputInterfaceAccept struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceAccept) Name() string { - return "FilterOutputInterfaceAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "ACCEPT"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceDrop tests that packets are not sent via interface -// matching the iptables rule. -type FilterOutputInterfaceDrop struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceDrop) Name() string { - return "FilterOutputInterfaceDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputInterface tests that packets are sent via interface which is -// not matching the interface name in the iptables rule. -type FilterOutputInterface struct{ localCase } - -var _ TestCase = (*FilterOutputInterface)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterface) Name() string { - return "FilterOutputInterface" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "lo", "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceBeginsWith tests that packets are not sent via an -// interface which begins with the given interface name. -type FilterOutputInterfaceBeginsWith struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceBeginsWith)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceBeginsWith) Name() string { - return "FilterOutputInterfaceBeginsWith" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "e+", "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputInterfaceInvertDrop tests that we selectively do not send -// packets via interface not matching the interface name. -type FilterOutputInterfaceInvertDrop struct{ baseCase } - -var _ TestCase = (*FilterOutputInterfaceInvertDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceInvertDrop) Name() string { - return "FilterOutputInterfaceInvertDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInterfaceInvertAccept tests that we can selectively send packets -// not matching the specific outgoing interface. -type FilterOutputInterfaceInvertAccept struct{ baseCase } - -var _ TestCase = (*FilterOutputInterfaceInvertAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceInvertAccept) Name() string { - return "FilterOutputInterfaceInvertAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} diff --git a/test/iptables/iptables.go b/test/iptables/iptables.go deleted file mode 100644 index 970587a02..000000000 --- a/test/iptables/iptables.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package iptables contains a set of iptables tests implemented as TestCases -package iptables - -import ( - "context" - "fmt" - "net" - "time" -) - -// IPExchangePort is the port the container listens on to receive the IP -// address of the local process. -const IPExchangePort = 2349 - -// TerminalStatement is the last statement in the test runner. -const TerminalStatement = "Finished!" - -// TestTimeout is the timeout used for all tests. -const TestTimeout = 10 * time.Second - -// NegativeTimeout is the time tests should wait to establish the negative -// case, i.e. that connections are not made. -const NegativeTimeout = 2 * time.Second - -// A TestCase contains one action to run in the container and one to run -// locally. The actions run concurrently and each must succeed for the test -// pass. -type TestCase interface { - // Name returns the name of the test. - Name() string - - // ContainerAction runs inside the container. It receives the IP of the - // local process. - ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error - - // LocalAction runs locally. It receives the IP of the container. - LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error - - // ContainerSufficient indicates whether ContainerAction's return value - // alone indicates whether the test succeeded. - ContainerSufficient() bool - - // LocalSufficient indicates whether LocalAction's return value alone - // indicates whether the test succeeded. - LocalSufficient() bool -} - -// baseCase provides defaults for ContainerSufficient and LocalSufficient when -// both actions are required to finish. -type baseCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*baseCase) ContainerSufficient() bool { - return false -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*baseCase) LocalSufficient() bool { - return false -} - -// localCase provides defaults for ContainerSufficient and LocalSufficient when -// only the local action is required to finish. -type localCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*localCase) ContainerSufficient() bool { - return false -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*localCase) LocalSufficient() bool { - return true -} - -// containerCase provides defaults for ContainerSufficient and LocalSufficient -// when only the container action is required to finish. -type containerCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*containerCase) ContainerSufficient() bool { - return true -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*containerCase) LocalSufficient() bool { - return false -} - -// Tests maps test names to TestCase. -// -// New TestCases are added by calling RegisterTestCase in an init function. -var Tests = map[string]TestCase{} - -// RegisterTestCase registers tc so it can be run. -func RegisterTestCase(tc TestCase) { - if _, ok := Tests[tc.Name()]; ok { - panic(fmt.Sprintf("TestCase %s already registered.", tc.Name())) - } - Tests[tc.Name()] = tc -} diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go deleted file mode 100644 index d6c69a319..000000000 --- a/test/iptables/iptables_test.go +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" - "reflect" - "sync" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// singleTest runs a TestCase. Each test follows a pattern: -// - Create a container. -// - Get the container's IP. -// - Send the container our IP. -// - Start a new goroutine running the local action of the test. -// - Wait for both the container and local actions to finish. -// -// Container output is logged to $TEST_UNDECLARED_OUTPUTS_DIR if it exists, or -// to stderr. -func singleTest(t *testing.T, test TestCase) { - for _, tc := range []bool{false, true} { - subtest := "IPv4" - if tc { - subtest = "IPv6" - } - t.Run(subtest, func(t *testing.T) { - iptablesTest(t, test, tc) - }) - } -} - -func iptablesTest(t *testing.T, test TestCase, ipv6 bool) { - if _, ok := Tests[test.Name()]; !ok { - t.Fatalf("no test found with name %q. Has it been registered?", test.Name()) - } - - // Wait for the local and container goroutines to finish. - var wg sync.WaitGroup - defer wg.Wait() - - ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) - defer cancel() - - d := dockerutil.MakeContainer(ctx, t) - defer func() { - if logs, err := d.Logs(context.Background()); err != nil { - t.Logf("Failed to retrieve container logs.") - } else { - t.Logf("=== Container logs: ===\n%s", logs) - } - // Use a new context, as cleanup should run even when we - // timeout. - d.CleanUp(context.Background()) - }() - - // Create and start the container. - opts := dockerutil.RunOpts{ - Image: "iptables", - CapAdd: []string{"NET_ADMIN"}, - } - d.CopyFiles(&opts, "/runner", "test/iptables/runner/runner") - args := []string{"/runner/runner", "-name", test.Name()} - if ipv6 { - args = append(args, "-ipv6") - } - if err := d.Spawn(ctx, opts, args...); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Get the container IP. - ip, err := d.FindIP(ctx, ipv6) - if err != nil { - // If ipv6 is not configured, don't fail. - if ipv6 && err == dockerutil.ErrNoIP { - t.Skipf("No ipv6 address is available.") - } - t.Fatalf("failed to get container IP: %v", err) - } - - // Give the container our IP. - if err := sendIP(ip); err != nil { - t.Fatalf("failed to send IP to container: %v", err) - } - - // Run our side of the test. - errCh := make(chan error, 2) - wg.Add(1) - go func() { - defer wg.Done() - if err := test.LocalAction(ctx, ip, ipv6); err != nil && !errors.Is(err, context.Canceled) { - errCh <- fmt.Errorf("LocalAction failed: %v", err) - } else { - errCh <- nil - } - if test.LocalSufficient() { - errCh <- nil - } - }() - - // Run the container side. - wg.Add(1) - go func() { - defer wg.Done() - // Wait for the final statement. This structure has the side - // effect that all container logs will appear within the - // individual test context. - if _, err := d.WaitForOutput(ctx, TerminalStatement, TestTimeout); err != nil && !errors.Is(err, context.Canceled) { - errCh <- fmt.Errorf("ContainerAction failed: %v", err) - } else { - errCh <- nil - } - if test.ContainerSufficient() { - errCh <- nil - } - }() - - for i := 0; i < 2; i++ { - select { - case err := <-errCh: - if err != nil { - t.Fatal(err) - } - } - } -} - -func sendIP(ip net.IP) error { - contAddr := net.TCPAddr{ - IP: ip, - Port: IPExchangePort, - } - var conn *net.TCPConn - // The container may not be listening when we first connect, so retry - // upon error. - cb := func() error { - c, err := net.DialTCP("tcp", nil, &contAddr) - conn = c - return err - } - if err := testutil.Poll(cb, TestTimeout); err != nil { - return fmt.Errorf("timed out waiting to send IP, most recent error: %v", err) - } - if _, err := conn.Write([]byte{0}); err != nil { - return fmt.Errorf("error writing to container: %v", err) - } - return nil -} - -func TestFilterInputDropUDP(t *testing.T) { - singleTest(t, &FilterInputDropUDP{}) -} - -func TestFilterInputDropUDPPort(t *testing.T) { - singleTest(t, &FilterInputDropUDPPort{}) -} - -func TestFilterInputDropDifferentUDPPort(t *testing.T) { - singleTest(t, &FilterInputDropDifferentUDPPort{}) -} - -func TestFilterInputDropAll(t *testing.T) { - singleTest(t, &FilterInputDropAll{}) -} - -func TestFilterInputDropOnlyUDP(t *testing.T) { - singleTest(t, &FilterInputDropOnlyUDP{}) -} - -func TestFilterInputDropTCPDestPort(t *testing.T) { - singleTest(t, &FilterInputDropTCPDestPort{}) -} - -func TestFilterInputDropTCPSrcPort(t *testing.T) { - singleTest(t, &FilterInputDropTCPSrcPort{}) -} - -func TestFilterInputCreateUserChain(t *testing.T) { - singleTest(t, &FilterInputCreateUserChain{}) -} - -func TestFilterInputDefaultPolicyAccept(t *testing.T) { - singleTest(t, &FilterInputDefaultPolicyAccept{}) -} - -func TestFilterInputDefaultPolicyDrop(t *testing.T) { - singleTest(t, &FilterInputDefaultPolicyDrop{}) -} - -func TestFilterInputReturnUnderflow(t *testing.T) { - singleTest(t, &FilterInputReturnUnderflow{}) -} - -func TestFilterOutputDropTCPDestPort(t *testing.T) { - singleTest(t, &FilterOutputDropTCPDestPort{}) -} - -func TestFilterOutputDropTCPSrcPort(t *testing.T) { - singleTest(t, &FilterOutputDropTCPSrcPort{}) -} - -func TestFilterOutputAcceptTCPOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptTCPOwner{}) -} - -func TestFilterOutputDropTCPOwner(t *testing.T) { - singleTest(t, &FilterOutputDropTCPOwner{}) -} - -func TestFilterOutputAcceptUDPOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptUDPOwner{}) -} - -func TestFilterOutputDropUDPOwner(t *testing.T) { - singleTest(t, &FilterOutputDropUDPOwner{}) -} - -func TestFilterOutputOwnerFail(t *testing.T) { - singleTest(t, &FilterOutputOwnerFail{}) -} - -func TestFilterOutputAcceptGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptGIDOwner{}) -} - -func TestFilterOutputDropGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputDropGIDOwner{}) -} - -func TestFilterOutputInvertGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertGIDOwner{}) -} - -func TestFilterOutputInvertUIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertUIDOwner{}) -} - -func TestFilterOutputInvertUIDAndGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertUIDAndGIDOwner{}) -} - -func TestFilterOutputInterfaceAccept(t *testing.T) { - singleTest(t, &FilterOutputInterfaceAccept{}) -} - -func TestFilterOutputInterfaceDrop(t *testing.T) { - singleTest(t, &FilterOutputInterfaceDrop{}) -} - -func TestFilterOutputInterface(t *testing.T) { - singleTest(t, &FilterOutputInterface{}) -} - -func TestFilterOutputInterfaceBeginsWith(t *testing.T) { - singleTest(t, &FilterOutputInterfaceBeginsWith{}) -} - -func TestFilterOutputInterfaceInvertDrop(t *testing.T) { - singleTest(t, &FilterOutputInterfaceInvertDrop{}) -} - -func TestFilterOutputInterfaceInvertAccept(t *testing.T) { - singleTest(t, &FilterOutputInterfaceInvertAccept{}) -} - -func TestJumpSerialize(t *testing.T) { - singleTest(t, &FilterInputSerializeJump{}) -} - -func TestJumpBasic(t *testing.T) { - singleTest(t, &FilterInputJumpBasic{}) -} - -func TestJumpReturn(t *testing.T) { - singleTest(t, &FilterInputJumpReturn{}) -} - -func TestJumpReturnDrop(t *testing.T) { - singleTest(t, &FilterInputJumpReturnDrop{}) -} - -func TestJumpBuiltin(t *testing.T) { - singleTest(t, &FilterInputJumpBuiltin{}) -} - -func TestJumpTwice(t *testing.T) { - singleTest(t, &FilterInputJumpTwice{}) -} - -func TestInputDestination(t *testing.T) { - singleTest(t, &FilterInputDestination{}) -} - -func TestInputInvertDestination(t *testing.T) { - singleTest(t, &FilterInputInvertDestination{}) -} - -func TestFilterOutputDestination(t *testing.T) { - singleTest(t, &FilterOutputDestination{}) -} - -func TestFilterOutputInvertDestination(t *testing.T) { - singleTest(t, &FilterOutputInvertDestination{}) -} - -func TestNATPreRedirectUDPPort(t *testing.T) { - singleTest(t, &NATPreRedirectUDPPort{}) -} - -func TestNATPreRedirectTCPPort(t *testing.T) { - singleTest(t, &NATPreRedirectTCPPort{}) -} - -func TestNATPreRedirectTCPOutgoing(t *testing.T) { - singleTest(t, &NATPreRedirectTCPOutgoing{}) -} - -func TestNATOutRedirectTCPIncoming(t *testing.T) { - singleTest(t, &NATOutRedirectTCPIncoming{}) -} -func TestNATOutRedirectUDPPort(t *testing.T) { - singleTest(t, &NATOutRedirectUDPPort{}) -} - -func TestNATOutRedirectTCPPort(t *testing.T) { - singleTest(t, &NATOutRedirectTCPPort{}) -} - -func TestNATDropUDP(t *testing.T) { - singleTest(t, &NATDropUDP{}) -} - -func TestNATAcceptAll(t *testing.T) { - singleTest(t, &NATAcceptAll{}) -} - -func TestNATOutRedirectIP(t *testing.T) { - singleTest(t, &NATOutRedirectIP{}) -} - -func TestNATOutDontRedirectIP(t *testing.T) { - singleTest(t, &NATOutDontRedirectIP{}) -} - -func TestNATOutRedirectInvert(t *testing.T) { - singleTest(t, &NATOutRedirectInvert{}) -} - -func TestNATPreRedirectIP(t *testing.T) { - singleTest(t, &NATPreRedirectIP{}) -} - -func TestNATPreDontRedirectIP(t *testing.T) { - singleTest(t, &NATPreDontRedirectIP{}) -} - -func TestNATPreRedirectInvert(t *testing.T) { - singleTest(t, &NATPreRedirectInvert{}) -} - -func TestNATRedirectRequiresProtocol(t *testing.T) { - singleTest(t, &NATRedirectRequiresProtocol{}) -} - -func TestNATLoopbackSkipsPrerouting(t *testing.T) { - singleTest(t, &NATLoopbackSkipsPrerouting{}) -} - -func TestInputSource(t *testing.T) { - singleTest(t, &FilterInputSource{}) -} - -func TestInputInvertSource(t *testing.T) { - singleTest(t, &FilterInputInvertSource{}) -} - -func TestInputInterfaceAccept(t *testing.T) { - singleTest(t, &FilterInputInterfaceAccept{}) -} - -func TestInputInterfaceDrop(t *testing.T) { - singleTest(t, &FilterInputInterfaceDrop{}) -} - -func TestInputInterface(t *testing.T) { - singleTest(t, &FilterInputInterface{}) -} - -func TestInputInterfaceBeginsWith(t *testing.T) { - singleTest(t, &FilterInputInterfaceBeginsWith{}) -} - -func TestInputInterfaceInvertDrop(t *testing.T) { - singleTest(t, &FilterInputInterfaceInvertDrop{}) -} - -func TestInputInterfaceInvertAccept(t *testing.T) { - singleTest(t, &FilterInputInterfaceInvertAccept{}) -} - -func TestFilterAddrs(t *testing.T) { - tcs := []struct { - ipv6 bool - addrs []string - want []string - }{ - { - ipv6: false, - addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"}, - want: []string{"192.168.0.1", "192.168.0.2"}, - }, - { - ipv6: true, - addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"}, - want: []string{"::1", "::2"}, - }, - } - - for _, tc := range tcs { - if got := filterAddrs(tc.addrs, tc.ipv6); !reflect.DeepEqual(got, tc.want) { - t.Errorf("%v with IPv6 %t: got %v, but wanted %v", tc.addrs, tc.ipv6, got, tc.want) - } - } -} - -func TestNATPreOriginalDst(t *testing.T) { - singleTest(t, &NATPreOriginalDst{}) -} - -func TestNATOutOriginalDst(t *testing.T) { - singleTest(t, &NATOutOriginalDst{}) -} - -func TestNATPreRECVORIGDSTADDR(t *testing.T) { - singleTest(t, &NATPreRECVORIGDSTADDR{}) -} - -func TestNATOutRECVORIGDSTADDR(t *testing.T) { - singleTest(t, &NATOutRECVORIGDSTADDR{}) -} diff --git a/test/iptables/iptables_unsafe.go b/test/iptables/iptables_unsafe.go deleted file mode 100644 index dd1a1c082..000000000 --- a/test/iptables/iptables_unsafe.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "fmt" - "unsafe" - - "golang.org/x/sys/unix" -) - -type originalDstError struct { - errno unix.Errno -} - -func (e originalDstError) Error() string { - return fmt.Sprintf("errno (%d) when calling getsockopt(SO_ORIGINAL_DST): %v", int(e.errno), e.errno.Error()) -} - -// SO_ORIGINAL_DST gets the original destination of a redirected packet via -// getsockopt. -const SO_ORIGINAL_DST = 80 - -func originalDestination4(connfd int) (unix.RawSockaddrInet4, error) { - var addr unix.RawSockaddrInet4 - var addrLen uint32 = unix.SizeofSockaddrInet4 - if errno := originalDestination(connfd, unix.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return unix.RawSockaddrInet4{}, originalDstError{errno} - } - return addr, nil -} - -func originalDestination6(connfd int) (unix.RawSockaddrInet6, error) { - var addr unix.RawSockaddrInet6 - var addrLen uint32 = unix.SizeofSockaddrInet6 - if errno := originalDestination(connfd, unix.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return unix.RawSockaddrInet6{}, originalDstError{errno} - } - return addr, nil -} - -func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) unix.Errno { - _, _, errno := unix.Syscall6( - unix.SYS_GETSOCKOPT, - uintptr(connfd), - level, - SO_ORIGINAL_DST, - uintptr(optval), - uintptr(unsafe.Pointer(optlen)), - 0) - return errno -} diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go deleted file mode 100644 index bba17b894..000000000 --- a/test/iptables/iptables_util.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "net" - "os/exec" - "strings" - "time" - - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// filterTable calls `ip{6}tables -t filter` with the given args. -func filterTable(ipv6 bool, args ...string) error { - return tableCmd(ipv6, "filter", args) -} - -// natTable calls `ip{6}tables -t nat` with the given args. -func natTable(ipv6 bool, args ...string) error { - return tableCmd(ipv6, "nat", args) -} - -func tableCmd(ipv6 bool, table string, args []string) error { - args = append([]string{"-t", table}, args...) - binary := "iptables" - if ipv6 { - binary = "ip6tables" - } - cmd := exec.Command(binary, args...) - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("error running iptables with args %v\nerror: %v\noutput: %s", args, err, string(out)) - } - return nil -} - -// filterTableRules is like filterTable, but runs multiple iptables commands. -func filterTableRules(ipv6 bool, argsList [][]string) error { - return tableRules(ipv6, "filter", argsList) -} - -// natTableRules is like natTable, but runs multiple iptables commands. -func natTableRules(ipv6 bool, argsList [][]string) error { - return tableRules(ipv6, "nat", argsList) -} - -func tableRules(ipv6 bool, table string, argsList [][]string) error { - for _, args := range argsList { - if err := tableCmd(ipv6, table, args); err != nil { - return err - } - } - return nil -} - -// listenUDP listens on a UDP port and returns the value of net.Conn.Read() for -// the first read on that port. -func listenUDP(ctx context.Context, port int, ipv6 bool) error { - localAddr := net.UDPAddr{ - Port: port, - } - conn, err := net.ListenUDP(udpNetwork(ipv6), &localAddr) - if err != nil { - return err - } - defer conn.Close() - - ch := make(chan error) - go func() { - _, err = conn.Read([]byte{0}) - ch <- err - }() - - select { - case err := <-ch: - return err - case <-ctx.Done(): - return ctx.Err() - } -} - -// sendUDPLoop sends 1 byte UDP packets repeatedly to the IP and port specified -// over a duration. -func sendUDPLoop(ctx context.Context, ip net.IP, port int, ipv6 bool) error { - remote := net.UDPAddr{ - IP: ip, - Port: port, - } - conn, err := net.DialUDP(udpNetwork(ipv6), nil, &remote) - if err != nil { - return err - } - defer conn.Close() - - for { - // This may return an error (connection refused) if the remote - // hasn't started listening yet or they're dropping our - // packets. So we ignore Write errors and depend on the remote - // to report a failure if it doesn't get a packet it needs. - conn.Write([]byte{0}) - select { - case <-ctx.Done(): - // Being cancelled or timing out isn't an error, as we - // cannot tell with UDP whether we succeeded. - return nil - // Continue looping. - case <-time.After(200 * time.Millisecond): - } - } -} - -// listenTCP listens for connections on a TCP port. -func listenTCP(ctx context.Context, port int, ipv6 bool) error { - localAddr := net.TCPAddr{ - Port: port, - } - - // Starts listening on port. - lConn, err := net.ListenTCP(tcpNetwork(ipv6), &localAddr) - if err != nil { - return err - } - defer lConn.Close() - - // Accept connections on port. - ch := make(chan error) - go func() { - conn, err := lConn.AcceptTCP() - ch <- err - conn.Close() - }() - - select { - case err := <-ch: - return err - case <-ctx.Done(): - return fmt.Errorf("timed out waiting for a connection at %#v: %w", localAddr, ctx.Err()) - } -} - -// connectTCP connects to the given IP and port from an ephemeral local address. -func connectTCP(ctx context.Context, ip net.IP, port int, ipv6 bool) error { - contAddr := net.TCPAddr{ - IP: ip, - Port: port, - } - // The container may not be listening when we first connect, so retry - // upon error. - callback := func() error { - var d net.Dialer - conn, err := d.DialContext(ctx, tcpNetwork(ipv6), contAddr.String()) - if conn != nil { - conn.Close() - } - return err - } - if err := testutil.PollContext(ctx, callback); err != nil { - return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %w", port, err) - } - - return nil -} - -// localAddrs returns a list of local network interface addresses. When ipv6 is -// true, only IPv6 addresses are returned. Otherwise only IPv4 addresses are -// returned. -func localAddrs(ipv6 bool) ([]string, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - addrStrs := make([]string, 0, len(addrs)) - for _, addr := range addrs { - // Add only IPv4 or only IPv6 addresses. - parts := strings.Split(addr.String(), "/") - if len(parts) != 2 { - return nil, fmt.Errorf("bad interface address: %q", addr.String()) - } - if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 { - addrStrs = append(addrStrs, addr.String()) - } - } - return filterAddrs(addrStrs, ipv6), nil -} - -func filterAddrs(addrs []string, ipv6 bool) []string { - addrStrs := make([]string, 0, len(addrs)) - for _, addr := range addrs { - // Add only IPv4 or only IPv6 addresses. - parts := strings.Split(addr, "/") - if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 { - addrStrs = append(addrStrs, parts[0]) - } - } - return addrStrs -} - -// getInterfaceName returns the name of the interface other than loopback. -func getInterfaceName() (string, bool) { - iface, ok := getNonLoopbackInterface() - if !ok { - return "", false - } - return iface.Name, true -} - -func getInterfaceAddrs(ipv6 bool) ([]net.IP, error) { - iface, ok := getNonLoopbackInterface() - if !ok { - return nil, errors.New("no non-loopback interface found") - } - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - // Get only IPv4 or IPv6 addresses. - ips := make([]net.IP, 0, len(addrs)) - for _, addr := range addrs { - parts := strings.Split(addr.String(), "/") - var ip net.IP - // To16() returns IPv4 addresses as IPv4-mapped IPv6 addresses. - // So we check whether To4() returns nil to test whether the - // address is v4 or v6. - if v4 := net.ParseIP(parts[0]).To4(); ipv6 && v4 == nil { - ip = net.ParseIP(parts[0]).To16() - } else { - ip = v4 - } - if ip != nil { - ips = append(ips, ip) - } - } - return ips, nil -} - -func getNonLoopbackInterface() (net.Interface, bool) { - if interfaces, err := net.Interfaces(); err == nil { - for _, intf := range interfaces { - if intf.Name != "lo" { - return intf, true - } - } - } - return net.Interface{}, false -} - -func htons(x uint16) uint16 { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf, x) - return binary.LittleEndian.Uint16(buf) -} - -func localIP(ipv6 bool) string { - if ipv6 { - return "::1" - } - return "127.0.0.1" -} - -func nowhereIP(ipv6 bool) string { - if ipv6 { - return "2001:db8::1" - } - return "192.0.2.1" -} - -// udpNetwork returns an IPv6 or IPv6 UDP network argument to net.Dial. -func udpNetwork(ipv6 bool) string { - if ipv6 { - return "udp6" - } - return "udp4" -} - -// tcpNetwork returns an IPv6 or IPv6 TCP network argument to net.Dial. -func tcpNetwork(ipv6 bool) string { - if ipv6 { - return "tcp6" - } - return "tcp4" -} diff --git a/test/iptables/nat.go b/test/iptables/nat.go deleted file mode 100644 index 70d8a1832..000000000 --- a/test/iptables/nat.go +++ /dev/null @@ -1,917 +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 iptables - -import ( - "context" - "errors" - "fmt" - "net" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/usermem" -) - -const redirectPort = 42 - -func init() { - RegisterTestCase(&NATPreRedirectUDPPort{}) - RegisterTestCase(&NATPreRedirectTCPPort{}) - RegisterTestCase(&NATPreRedirectTCPOutgoing{}) - RegisterTestCase(&NATOutRedirectTCPIncoming{}) - RegisterTestCase(&NATOutRedirectUDPPort{}) - RegisterTestCase(&NATOutRedirectTCPPort{}) - RegisterTestCase(&NATDropUDP{}) - RegisterTestCase(&NATAcceptAll{}) - RegisterTestCase(&NATPreRedirectIP{}) - RegisterTestCase(&NATPreDontRedirectIP{}) - RegisterTestCase(&NATPreRedirectInvert{}) - RegisterTestCase(&NATOutRedirectIP{}) - RegisterTestCase(&NATOutDontRedirectIP{}) - RegisterTestCase(&NATOutRedirectInvert{}) - RegisterTestCase(&NATRedirectRequiresProtocol{}) - RegisterTestCase(&NATLoopbackSkipsPrerouting{}) - RegisterTestCase(&NATPreOriginalDst{}) - RegisterTestCase(&NATOutOriginalDst{}) - RegisterTestCase(&NATPreRECVORIGDSTADDR{}) - RegisterTestCase(&NATOutRECVORIGDSTADDR{}) -} - -// NATPreRedirectUDPPort tests that packets are redirected to different port. -type NATPreRedirectUDPPort struct{ containerCase } - -var _ TestCase = (*NATPreRedirectUDPPort)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectUDPPort) Name() string { - return "NATPreRedirectUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - if err := listenUDP(ctx, redirectPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", redirectPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATPreRedirectTCPPort tests that connections are redirected on specified ports. -type NATPreRedirectTCPPort struct{ baseCase } - -var _ TestCase = (*NATPreRedirectTCPPort)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectTCPPort) Name() string { - return "NATPreRedirectTCPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - // Listen for TCP packets on redirect port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, dropPort, ipv6) -} - -// NATPreRedirectTCPOutgoing verifies that outgoing TCP connections aren't -// affected by PREROUTING connection tracking. -type NATPreRedirectTCPOutgoing struct{ baseCase } - -var _ TestCase = (*NATPreRedirectTCPOutgoing)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectTCPOutgoing) Name() string { - return "NATPreRedirectTCPOutgoing" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectTCPOutgoing) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect all incoming TCP traffic to a closed port. - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection to the host process. - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectTCPOutgoing) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenTCP(ctx, acceptPort, ipv6) -} - -// NATOutRedirectTCPIncoming verifies that incoming TCP connections aren't -// affected by OUTPUT connection tracking. -type NATOutRedirectTCPIncoming struct{ baseCase } - -var _ TestCase = (*NATOutRedirectTCPIncoming)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectTCPIncoming) Name() string { - return "NATOutRedirectTCPIncoming" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectTCPIncoming) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect all outgoing TCP traffic to a closed port. - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection to the host process. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectTCPIncoming) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// NATOutRedirectUDPPort tests that packets are redirected to different port. -type NATOutRedirectUDPPort struct{ containerCase } - -var _ TestCase = (*NATOutRedirectUDPPort)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectUDPPort) Name() string { - return "NATOutRedirectUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATDropUDP tests that packets are not received in ports other than redirect -// port. -type NATDropUDP struct{ containerCase } - -var _ TestCase = (*NATDropUDP)(nil) - -// Name implements TestCase.Name. -func (*NATDropUDP) Name() string { - return "NATDropUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been redirected to port %d", acceptPort, redirectPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATAcceptAll tests that all UDP packets are accepted. -type NATAcceptAll struct{ containerCase } - -var _ TestCase = (*NATAcceptAll)(nil) - -// Name implements TestCase.Name. -func (*NATAcceptAll) Name() string { - return "NATAcceptAll" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATAcceptAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil { - return err - } - - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATAcceptAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATOutRedirectIP uses iptables to select packets based on destination IP and -// redirects them. -type NATOutRedirectIP struct{ baseCase } - -var _ TestCase = (*NATOutRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectIP) Name() string { - return "NATOutRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect OUTPUT packets to a listening localhost port. - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), - "-A", "OUTPUT", - "-d", nowhereIP(ipv6), - "-p", "udp", - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATOutDontRedirectIP tests that iptables matching with "-d" does not match -// packets it shouldn't. -type NATOutDontRedirectIP struct{ localCase } - -var _ TestCase = (*NATOutDontRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATOutDontRedirectIP) Name() string { - return "NATOutDontRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-d", localIP(ipv6), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// NATOutRedirectInvert tests that iptables can match with "! -d". -type NATOutRedirectInvert struct{ baseCase } - -var _ TestCase = (*NATOutRedirectInvert)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectInvert) Name() string { - return "NATOutRedirectInvert" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect OUTPUT packets to a listening localhost port. - dest := "192.0.2.2" - if ipv6 { - dest = "2001:db8::2" - } - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), - "-A", "OUTPUT", - "!", "-d", dest, - "-p", "udp", - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATPreRedirectIP tests that we can use iptables to select packets based on -// destination IP and redirect them. -type NATPreRedirectIP struct{ containerCase } - -var _ TestCase = (*NATPreRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectIP) Name() string { - return "NATPreRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - addrs, err := localAddrs(ipv6) - if err != nil { - return err - } - - var rules [][]string - for _, addr := range addrs { - rules = append(rules, []string{"-A", "PREROUTING", "-p", "udp", "-d", addr, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)}) - } - if err := natTableRules(ipv6, rules); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// NATPreDontRedirectIP tests that iptables matching with "-d" does not match -// packets it shouldn't. -type NATPreDontRedirectIP struct{ containerCase } - -var _ TestCase = (*NATPreDontRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATPreDontRedirectIP) Name() string { - return "NATPreDontRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATPreRedirectInvert tests that iptables can match with "! -d". -type NATPreRedirectInvert struct{ containerCase } - -var _ TestCase = (*NATPreRedirectInvert)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectInvert) Name() string { - return "NATPreRedirectInvert" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "!", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// NATRedirectRequiresProtocol tests that use of the --to-ports flag requires a -// protocol to be specified with -p. -type NATRedirectRequiresProtocol struct{ baseCase } - -var _ TestCase = (*NATRedirectRequiresProtocol)(nil) - -// Name implements TestCase.Name. -func (*NATRedirectRequiresProtocol) Name() string { - return "NATRedirectRequiresProtocol" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATRedirectRequiresProtocol) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil { - return errors.New("expected an error using REDIRECT --to-ports without a protocol") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATRedirectRequiresProtocol) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATOutRedirectTCPPort tests that connections are redirected on specified ports. -type NATOutRedirectTCPPort struct{ baseCase } - -var _ TestCase = (*NATOutRedirectTCPPort)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectTCPPort) Name() string { - return "NATOutRedirectTCPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - localAddr := net.TCPAddr{ - IP: net.ParseIP(localIP(ipv6)), - Port: acceptPort, - } - - // Starts listening on port. - lConn, err := net.ListenTCP("tcp", &localAddr) - if err != nil { - return err - } - defer lConn.Close() - - // Accept connections on port. - if err := connectTCP(ctx, ip, dropPort, ipv6); err != nil { - return err - } - - conn, err := lConn.AcceptTCP() - if err != nil { - return err - } - conn.Close() - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return nil -} - -// NATLoopbackSkipsPrerouting tests that packets sent via loopback aren't -// affected by PREROUTING rules. -type NATLoopbackSkipsPrerouting struct{ baseCase } - -var _ TestCase = (*NATLoopbackSkipsPrerouting)(nil) - -// Name implements TestCase.Name. -func (*NATLoopbackSkipsPrerouting) Name() string { - return "NATLoopbackSkipsPrerouting" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATLoopbackSkipsPrerouting) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect anything sent to localhost to an unused port. - dest := []byte{127, 0, 0, 1} - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection via localhost. If the PREROUTING rule did apply to - // loopback traffic, the connection would fail. - sendCh := make(chan error) - go func() { - sendCh <- connectTCP(ctx, dest, acceptPort, ipv6) - }() - - if err := listenTCP(ctx, acceptPort, ipv6); err != nil { - return err - } - return <-sendCh -} - -// LocalAction implements TestCase.LocalAction. -func (*NATLoopbackSkipsPrerouting) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination -// of PREROUTING NATted packets. -type NATPreOriginalDst struct{ baseCase } - -var _ TestCase = (*NATPreOriginalDst)(nil) - -// Name implements TestCase.Name. -func (*NATPreOriginalDst) Name() string { - return "NATPreOriginalDst" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect incoming TCP connections to acceptPort. - if err := natTable(ipv6, "-A", "PREROUTING", - "-p", "tcp", - "--destination-port", fmt.Sprintf("%d", dropPort), - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - addrs, err := getInterfaceAddrs(ipv6) - if err != nil { - return err - } - return listenForRedirectedConn(ctx, ipv6, addrs) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, dropPort, ipv6) -} - -// NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination -// of OUTBOUND NATted packets. -type NATOutOriginalDst struct{ baseCase } - -var _ TestCase = (*NATOutOriginalDst)(nil) - -// Name implements TestCase.Name. -func (*NATOutOriginalDst) Name() string { - return "NATOutOriginalDst" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect incoming TCP connections to acceptPort. - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - connCh := make(chan error) - go func() { - connCh <- connectTCP(ctx, ip, dropPort, ipv6) - }() - - if err := listenForRedirectedConn(ctx, ipv6, []net.IP{ip}); err != nil { - return err - } - return <-connCh -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.IP) error { - // The net package doesn't give guaranteed access to the connection's - // underlying FD, and thus we cannot call getsockopt. We have to use - // traditional syscalls. - - // Create the listening socket, bind, listen, and accept. - family := unix.AF_INET - if ipv6 { - family = unix.AF_INET6 - } - sockfd, err := unix.Socket(family, unix.SOCK_STREAM, 0) - if err != nil { - return err - } - defer unix.Close(sockfd) - - var bindAddr unix.Sockaddr - if ipv6 { - bindAddr = &unix.SockaddrInet6{ - Port: acceptPort, - Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any - } - } else { - bindAddr = &unix.SockaddrInet4{ - Port: acceptPort, - Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY - } - } - if err := unix.Bind(sockfd, bindAddr); err != nil { - return err - } - - if err := unix.Listen(sockfd, 1); err != nil { - return err - } - - // Block on accept() in another goroutine. - connCh := make(chan int) - errCh := make(chan error) - go func() { - for { - connFD, _, err := unix.Accept(sockfd) - if errors.Is(err, unix.EINTR) { - continue - } - if err != nil { - errCh <- err - return - } - connCh <- connFD - return - } - }() - - // Wait for accept() to return or for the context to finish. - var connFD int - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - return err - case connFD = <-connCh: - } - defer unix.Close(connFD) - - // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST - // indicates the packet was sent to originalDst:dropPort. - if ipv6 { - got, err := originalDestination6(connFD) - if err != nil { - return err - } - return addrMatches6(got, originalDsts, dropPort) - } - - got, err := originalDestination4(connFD) - if err != nil { - return err - } - return addrMatches4(got, originalDsts, dropPort) -} - -// loopbackTests runs an iptables rule and ensures that packets sent to -// dest:dropPort are received by localhost:acceptPort. -func loopbackTest(ctx context.Context, ipv6 bool, dest net.IP, args ...string) error { - if err := natTable(ipv6, args...); err != nil { - return err - } - sendCh := make(chan error, 1) - listenCh := make(chan error, 1) - go func() { - sendCh <- sendUDPLoop(ctx, dest, dropPort, ipv6) - }() - go func() { - listenCh <- listenUDP(ctx, acceptPort, ipv6) - }() - select { - case err := <-listenCh: - return err - case err := <-sendCh: - return err - } -} - -// NATPreRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT -// address on the PREROUTING chain. -type NATPreRECVORIGDSTADDR struct{ containerCase } - -var _ TestCase = (*NATPreRECVORIGDSTADDR)(nil) - -// Name implements TestCase.Name. -func (*NATPreRECVORIGDSTADDR) Name() string { - return "NATPreRECVORIGDSTADDR" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - if err := recvWithRECVORIGDSTADDR(ctx, ipv6, nil, redirectPort); err != nil { - return err - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATOutRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT -// address on the OUTPUT chain. -type NATOutRECVORIGDSTADDR struct{ containerCase } - -var _ TestCase = (*NATOutRECVORIGDSTADDR)(nil) - -// Name implements TestCase.Name. -func (*NATOutRECVORIGDSTADDR) Name() string { - return "NATOutRECVORIGDSTADDR" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - sendCh := make(chan error) - go func() { - // Packets will be sent to a non-container IP and redirected - // back to the container. - sendCh <- sendUDPLoop(ctx, ip, acceptPort, ipv6) - }() - - expectedIP := &net.IP{127, 0, 0, 1} - if ipv6 { - expectedIP = &net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - } - if err := recvWithRECVORIGDSTADDR(ctx, ipv6, expectedIP, redirectPort); err != nil { - return err - } - - select { - case err := <-sendCh: - return err - default: - return nil - } -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -func recvWithRECVORIGDSTADDR(ctx context.Context, ipv6 bool, expectedDst *net.IP, port uint16) error { - // The net package doesn't give guaranteed access to a connection's - // underlying FD, and thus we cannot call getsockopt. We have to use - // traditional syscalls for IP_RECVORIGDSTADDR. - - // Create the listening socket. - var ( - family = unix.AF_INET - level = unix.SOL_IP - option = unix.IP_RECVORIGDSTADDR - bindAddr unix.Sockaddr = &unix.SockaddrInet4{ - Port: int(port), - Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY - } - ) - if ipv6 { - family = unix.AF_INET6 - level = unix.SOL_IPV6 - option = 74 // IPV6_RECVORIGDSTADDR, which is missing from the syscall package. - bindAddr = &unix.SockaddrInet6{ - Port: int(port), - Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any - } - } - sockfd, err := unix.Socket(family, unix.SOCK_DGRAM, 0) - if err != nil { - return fmt.Errorf("failed Socket(%d, %d, 0): %w", family, unix.SOCK_DGRAM, err) - } - defer unix.Close(sockfd) - - if err := unix.Bind(sockfd, bindAddr); err != nil { - return fmt.Errorf("failed Bind(%d, %+v): %v", sockfd, bindAddr, err) - } - - // Enable IP_RECVORIGDSTADDR. - if err := unix.SetsockoptInt(sockfd, level, option, 1); err != nil { - return fmt.Errorf("failed SetsockoptByte(%d, %d, %d, 1): %v", sockfd, level, option, err) - } - - addrCh := make(chan interface{}) - errCh := make(chan error) - go func() { - var addr interface{} - var err error - if ipv6 { - addr, err = recvOrigDstAddr6(sockfd) - } else { - addr, err = recvOrigDstAddr4(sockfd) - } - if err != nil { - errCh <- err - } else { - addrCh <- addr - } - }() - - // Wait to receive a packet. - var addr interface{} - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - return err - case addr = <-addrCh: - } - - // Get a list of local IPs to verify that the packet now appears to have - // been sent to us. - var localAddrs []net.IP - if expectedDst != nil { - localAddrs = []net.IP{*expectedDst} - } else { - localAddrs, err = getInterfaceAddrs(ipv6) - if err != nil { - return fmt.Errorf("failed to get local interfaces: %w", err) - } - } - - // Verify that the address has the post-NAT port and address. - if ipv6 { - return addrMatches6(addr.(unix.RawSockaddrInet6), localAddrs, redirectPort) - } - return addrMatches4(addr.(unix.RawSockaddrInet4), localAddrs, redirectPort) -} - -func recvOrigDstAddr4(sockfd int) (unix.RawSockaddrInet4, error) { - buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet4) - if err != nil { - return unix.RawSockaddrInet4{}, err - } - var addr unix.RawSockaddrInet4 - binary.Unmarshal(buf, usermem.ByteOrder, &addr) - return addr, nil -} - -func recvOrigDstAddr6(sockfd int) (unix.RawSockaddrInet6, error) { - buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet6) - if err != nil { - return unix.RawSockaddrInet6{}, err - } - var addr unix.RawSockaddrInet6 - binary.Unmarshal(buf, usermem.ByteOrder, &addr) - return addr, nil -} - -func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { - buf := make([]byte, 64) - oob := make([]byte, unix.CmsgSpace(addrSize)) - for { - _, oobn, _, _, err := unix.Recvmsg( - sockfd, - buf, // Message buffer. - oob, // Out-of-band buffer. - 0) // Flags. - if errors.Is(err, unix.EINTR) { - continue - } - if err != nil { - return nil, fmt.Errorf("failed when calling Recvmsg: %w", err) - } - oob = oob[:oobn] - - // Parse out the control message. - msgs, err := unix.ParseSocketControlMessage(oob) - if err != nil { - return nil, fmt.Errorf("failed to parse control message: %w", err) - } - return msgs[0].Data, nil - } -} - -func addrMatches4(got unix.RawSockaddrInet4, wantAddrs []net.IP, port uint16) error { - for _, wantAddr := range wantAddrs { - want := unix.RawSockaddrInet4{ - Family: unix.AF_INET, - Port: htons(port), - } - copy(want.Addr[:], wantAddr.To4()) - if got == want { - return nil - } - } - return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) -} - -func addrMatches6(got unix.RawSockaddrInet6, wantAddrs []net.IP, port uint16) error { - for _, wantAddr := range wantAddrs { - want := unix.RawSockaddrInet6{ - Family: unix.AF_INET6, - Port: htons(port), - } - copy(want.Addr[:], wantAddr.To16()) - if got == want { - return nil - } - } - return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) -} diff --git a/test/iptables/runner/BUILD b/test/iptables/runner/BUILD deleted file mode 100644 index 24504a1b9..000000000 --- a/test/iptables/runner/BUILD +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "runner", - testonly = 1, - srcs = ["main.go"], - pure = True, - visibility = ["//test/iptables:__subpackages__"], - deps = ["//test/iptables"], -) diff --git a/test/iptables/runner/main.go b/test/iptables/runner/main.go deleted file mode 100644 index 9ae2d1b4d..000000000 --- a/test/iptables/runner/main.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package main runs iptables tests from within a docker container. -package main - -import ( - "context" - "flag" - "fmt" - "log" - "net" - - "gvisor.dev/gvisor/test/iptables" -) - -var ( - name = flag.String("name", "", "name of the test to run") - ipv6 = flag.Bool("ipv6", false, "whether the test utilizes ip6tables") -) - -func main() { - flag.Parse() - - // Find out which test we're running. - test, ok := iptables.Tests[*name] - if !ok { - log.Fatalf("No test found named %q", *name) - } - log.Printf("Running test %q", *name) - - // Get the IP of the local process. - ip, err := getIP() - if err != nil { - log.Fatal(err) - } - - // Run the test. - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - if err := test.ContainerAction(ctx, ip, *ipv6); err != nil { - log.Fatalf("Failed running test %q: %v", *name, err) - } - - // Emit the final line. - log.Printf("%s", iptables.TerminalStatement) -} - -// getIP listens for a connection from the local process and returns the source -// IP of that connection. -func getIP() (net.IP, error) { - localAddr := net.TCPAddr{ - Port: iptables.IPExchangePort, - } - listener, err := net.ListenTCP("tcp", &localAddr) - if err != nil { - return net.IP{}, fmt.Errorf("failed listening for IP: %v", err) - } - defer listener.Close() - conn, err := listener.AcceptTCP() - if err != nil { - return net.IP{}, fmt.Errorf("failed accepting IP: %v", err) - } - defer conn.Close() - log.Printf("Connected to %v", conn.RemoteAddr()) - - return conn.RemoteAddr().(*net.TCPAddr).IP, nil -} diff --git a/test/kubernetes/gvisor-injection-admission-webhook.yaml b/test/kubernetes/gvisor-injection-admission-webhook.yaml deleted file mode 100644 index 691f02dda..000000000 --- a/test/kubernetes/gvisor-injection-admission-webhook.yaml +++ /dev/null @@ -1,89 +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. - ---- -apiVersion: v1 -kind: Namespace -metadata: - name: e2e - labels: - name: e2e ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: gvisor-injection-admission-webhook - namespace: e2e ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: gvisor-injection-admission-webhook -rules: -- apiGroups: [ admissionregistration.k8s.io ] - resources: [ mutatingwebhookconfigurations ] - verbs: [ create ] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: gvisor-injection-admission-webhook - namespace: e2e -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: gvisor-injection-admission-webhook -subjects: -- kind: ServiceAccount - name: gvisor-injection-admission-webhook - namespace: e2e ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: gvisor-injection-admission-webhook - namespace: e2e - labels: - app: gvisor-injection-admission-webhook -spec: - replicas: 1 - selector: - matchLabels: - app: gvisor-injection-admission-webhook - template: - metadata: - labels: - app: gvisor-injection-admission-webhook - spec: - containers: - - name: webhook - image: gcr.io/gke-gvisor/gvisor-injection-admission-webhook:54ce9bd - args: - - --log-level=debug - ports: - - containerPort: 8443 - serviceAccountName: gvisor-injection-admission-webhook ---- -kind: Service -apiVersion: v1 -metadata: - name: gvisor-injection-admission-webhook - namespace: e2e -spec: - selector: - app: gvisor-injection-admission-webhook - ports: - - protocol: TCP - port: 443 - targetPort: 8443 diff --git a/test/packetdrill/BUILD b/test/packetdrill/BUILD deleted file mode 100644 index 5d95516ee..000000000 --- a/test/packetdrill/BUILD +++ /dev/null @@ -1,54 +0,0 @@ -load("//tools:defs.bzl", "bzl_library") -load("//test/packetdrill:defs.bzl", "packetdrill_test") - -package(licenses = ["notice"]) - -packetdrill_test( - name = "packetdrill_sanity_test", - scripts = ["sanity_test.pkt"], -) - -packetdrill_test( - name = "accept_ack_drop_test", - scripts = ["accept_ack_drop.pkt"], -) - -packetdrill_test( - name = "fin_wait2_timeout_test", - scripts = ["fin_wait2_timeout.pkt"], -) - -packetdrill_test( - name = "listen_close_before_handshake_complete_test", - scripts = ["listen_close_before_handshake_complete.pkt"], -) - -packetdrill_test( - name = "no_rst_to_rst_test", - scripts = ["no_rst_to_rst.pkt"], -) - -packetdrill_test( - name = "tcp_defer_accept_test", - scripts = ["tcp_defer_accept.pkt"], -) - -packetdrill_test( - name = "tcp_defer_accept_timeout_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"], - visibility = ["//visibility:private"], -) diff --git a/test/packetdrill/accept_ack_drop.pkt b/test/packetdrill/accept_ack_drop.pkt deleted file mode 100644 index 76e638fd4..000000000 --- a/test/packetdrill/accept_ack_drop.pkt +++ /dev/null @@ -1,27 +0,0 @@ -// Test that the accept works if the final ACK is dropped and an ack with data -// follows the dropped ack. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 bind(3, ..., ...) = 0 - -// Set backlog to 1 so that we can easily test. -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0.0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0.0 > S. 0:0(0) ack 1 <...> - -+0.0 < . 1:5(4) ack 1 win 257 -+0.0 > . 1:1(0) ack 5 <...> - -// This should cause connection to transition to connected state. -+0.000 accept(3, ..., ...) = 4 -+0.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 - -// Now read the data and we should get 4 bytes. -+0.000 read(4,..., 4) = 4 -+0.000 close(4) = 0 - -+0.0 > F. 1:1(0) ack 5 <...> -+0.0 < F. 5:5(0) ack 2 win 257 -+0.01 > . 2:2(0) ack 6 <...>
\ No newline at end of file diff --git a/test/packetdrill/defs.bzl b/test/packetdrill/defs.bzl deleted file mode 100644 index a6cbcc376..000000000 --- a/test/packetdrill/defs.bzl +++ /dev/null @@ -1,89 +0,0 @@ -"""Defines a rule for packetdrill test targets.""" - -def _packetdrill_test_impl(ctx): - test_runner = ctx.executable._test_runner - runner = ctx.actions.declare_file("%s-runner" % ctx.label.name) - - script_paths = [] - for script in ctx.files.scripts: - script_paths.append(script.short_path) - runner_content = "\n".join([ - "#!/bin/bash", - # This test will run part in a distinct user namespace. This can cause - # permission problems, because all runfiles may not be owned by the - # current user, and no other users will be mapped in that namespace. - # Make sure that everything is readable here. - "find . -type f -exec chmod a+rx {} \\;", - "find . -type d -exec chmod a+rx {} \\;", - "%s %s --init_script %s \"$@\" -- %s\n" % ( - test_runner.short_path, - " ".join(ctx.attr.flags), - ctx.files._init_script[0].short_path, - " ".join(script_paths), - ), - ]) - ctx.actions.write(runner, runner_content, is_executable = True) - - transitive_files = depset() - if hasattr(ctx.attr._test_runner, "data_runfiles"): - transitive_files = ctx.attr._test_runner.data_runfiles.files - runfiles = ctx.runfiles( - files = [test_runner] + ctx.files._init_script + ctx.files.scripts, - transitive_files = transitive_files, - collect_default = True, - collect_data = True, - ) - return [DefaultInfo(executable = runner, runfiles = runfiles)] - -_packetdrill_test = rule( - attrs = { - "_test_runner": attr.label( - executable = True, - cfg = "host", - allow_files = True, - default = "packetdrill_test.sh", - ), - "_init_script": attr.label( - allow_single_file = True, - default = "packetdrill_setup.sh", - ), - "flags": attr.string_list( - mandatory = False, - default = [], - ), - "scripts": attr.label_list( - mandatory = True, - allow_files = True, - ), - }, - test = True, - implementation = _packetdrill_test_impl, -) - -PACKETDRILL_TAGS = [ - "local", - "manual", - "packetdrill", -] - -def packetdrill_linux_test(name, **kwargs): - if "tags" not in kwargs: - kwargs["tags"] = PACKETDRILL_TAGS - _packetdrill_test( - name = name, - flags = ["--dut_platform", "linux"], - **kwargs - ) - -def packetdrill_netstack_test(name, **kwargs): - if "tags" not in kwargs: - kwargs["tags"] = PACKETDRILL_TAGS - _packetdrill_test( - name = name, - flags = ["--dut_platform", "netstack"], - **kwargs - ) - -def packetdrill_test(name, **kwargs): - packetdrill_linux_test(name + "_linux_test", **kwargs) - packetdrill_netstack_test(name + "_netstack_test", **kwargs) diff --git a/test/packetdrill/fin_wait2_timeout.pkt b/test/packetdrill/fin_wait2_timeout.pkt deleted file mode 100644 index 93ab08575..000000000 --- a/test/packetdrill/fin_wait2_timeout.pkt +++ /dev/null @@ -1,23 +0,0 @@ -// Test that a socket in FIN_WAIT_2 eventually times out and a subsequent -// packet generates a RST. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 bind(3, ..., ...) = 0 - -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0 > S. 0:0(0) ack 1 <...> -+0 < P. 1:1(0) ack 1 win 257 - -+0.100 accept(3, ..., ...) = 4 -// set FIN_WAIT2 timeout to 1 seconds. -+0.100 setsockopt(4, SOL_TCP, TCP_LINGER2, [1], 4) = 0 -+0 close(4) = 0 - -+0 > F. 1:1(0) ack 1 <...> -+0 < . 1:1(0) ack 2 win 257 - -+2 < . 1:1(0) ack 2 win 257 -+0 > R 2:2(0) win 0 diff --git a/test/packetdrill/listen_close_before_handshake_complete.pkt b/test/packetdrill/listen_close_before_handshake_complete.pkt deleted file mode 100644 index 51c3f1a32..000000000 --- a/test/packetdrill/listen_close_before_handshake_complete.pkt +++ /dev/null @@ -1,31 +0,0 @@ -// Test that closing a listening socket closes any connections in SYN-RCVD -// state and any packets bound for these connections generate a RESET. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 bind(3, ..., ...) = 0 - -// Set backlog to 1 so that we can easily test. -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0 > S. 0:0(0) ack 1 <...> - -+0.100 close(3) = 0 -+0.1 < P. 1:1(0) ack 1 win 257 - -// Linux generates a reset with no ack number/bit set. This is contradictory to -// what is specified in Rule 1 under Reset Generation in -// https://tools.ietf.org/html/rfc793#section-3.4. -// "1. If the connection does not exist (CLOSED) then a reset is sent -// in response to any incoming segment except another reset. In -// particular, SYNs addressed to a non-existent connection are rejected -// by this means. -// -// If the incoming segment has an ACK field, the reset takes its -// sequence number from the ACK field of the segment, otherwise the -// reset has sequence number zero and the ACK field is set to the sum -// of the sequence number and segment length of the incoming segment. -// The connection remains in the CLOSED state." - -+0.0 > R 1:1(0) win 0
\ No newline at end of file diff --git a/test/packetdrill/no_rst_to_rst.pkt b/test/packetdrill/no_rst_to_rst.pkt deleted file mode 100644 index 612747827..000000000 --- a/test/packetdrill/no_rst_to_rst.pkt +++ /dev/null @@ -1,36 +0,0 @@ -// Test a RST is not generated in response to a RST and a RST is correctly -// generated when an accepted endpoint is RST due to an incoming RST. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 bind(3, ..., ...) = 0 - -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0 > S. 0:0(0) ack 1 <...> -+0 < P. 1:1(0) ack 1 win 257 - -+0.100 accept(3, ..., ...) = 4 - -+0.200 < R 1:1(0) win 0 - -+0.300 read(4,..., 4) = -1 ECONNRESET (Connection Reset by Peer) - -+0.00 < . 1:1(0) ack 1 win 257 - -// Linux generates a reset with no ack number/bit set. This is contradictory to -// what is specified in Rule 1 under Reset Generation in -// https://tools.ietf.org/html/rfc793#section-3.4. -// "1. If the connection does not exist (CLOSED) then a reset is sent -// in response to any incoming segment except another reset. In -// particular, SYNs addressed to a non-existent connection are rejected -// by this means. -// -// If the incoming segment has an ACK field, the reset takes its -// sequence number from the ACK field of the segment, otherwise the -// reset has sequence number zero and the ACK field is set to the sum -// of the sequence number and segment length of the incoming segment. -// The connection remains in the CLOSED state." - -+0.00 > R 1:1(0) win 0
\ No newline at end of file diff --git a/test/packetdrill/packetdrill_setup.sh b/test/packetdrill/packetdrill_setup.sh deleted file mode 100755 index b858072f0..000000000 --- a/test/packetdrill/packetdrill_setup.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script runs both within the sentry context and natively. It should tweak -# TCP parameters to match expectations found in the script files. -sysctl -q net.ipv4.tcp_sack=1 -sysctl -q net.ipv4.tcp_rmem="4096 2097152 $((8*1024*1024))" -sysctl -q net.ipv4.tcp_wmem="4096 2097152 $((8*1024*1024))" - -# There may be errors from the above, but they will show up in the test logs and -# we always want to proceed from this point. It's possible that values were -# already set correctly and the nodes were not available in the namespace. -exit 0 diff --git a/test/packetdrill/packetdrill_test.sh b/test/packetdrill/packetdrill_test.sh deleted file mode 100755 index d25cad83a..000000000 --- a/test/packetdrill/packetdrill_test.sh +++ /dev/null @@ -1,231 +0,0 @@ -#!/bin/bash - -# Copyright 2020 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Run a packetdrill test. Two docker containers are made, one for the -# Device-Under-Test (DUT) and one for the test runner. Each is attached with -# two networks, one for control packets that aid the test and one for test -# packets which are sent as part of the test and observed for correctness. - -set -euxo pipefail - -function failure() { - local lineno=$1 - local msg=$2 - local filename="$0" - echo "FAIL: $filename:$lineno: $msg" -} -trap 'failure ${LINENO} "$BASH_COMMAND"' ERR - -declare -r LONGOPTS="dut_platform:,init_script:,runtime:,partition:,total_partitions:" - -# Don't use declare below so that the error from getopt will end the script. -PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@") - -eval set -- "$PARSED" - -while true; do - case "$1" in - --dut_platform) - # Either "linux" or "netstack". - declare -r DUT_PLATFORM="$2" - shift 2 - ;; - --init_script) - declare -r INIT_SCRIPT="$2" - shift 2 - ;; - --runtime) - declare RUNTIME="$2" - shift 2 - ;; - --partition) - # Ignored. - shift 2 - ;; - --total_partitions) - # Ignored. - shift 2 - ;; - --) - shift - break - ;; - *) - echo "Programming error" - exit 3 - esac -done - -# All the other arguments are scripts. -declare -r scripts="$@" - -# Check that the required flags are defined in a way that is safe for "set -u". -if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then - if [[ -z "${RUNTIME-}" ]]; then - echo "FAIL: Missing --runtime argument: ${RUNTIME-}" - exit 2 - fi - declare -r RUNTIME_ARG="--runtime ${RUNTIME}" -elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then - declare -r RUNTIME_ARG="" -else - echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}" - exit 2 -fi -if [[ ! -x "${INIT_SCRIPT-}" ]]; then - echo "FAIL: Bad or missing --init_script: ${INIT_SCRIPT-}" - exit 2 -fi - -function new_net_prefix() { - # Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24. - echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)" -} - -# Variables specific to the control network and interface start with CTRL_. -# Variables specific to the test network and interface start with TEST_. -# Variables specific to the DUT start with DUT_. -# Variables specific to the test runner start with TEST_RUNNER_. -declare -r PACKETDRILL="/packetdrill/gtests/net/packetdrill/packetdrill" -# Use random numbers so that test networks don't collide. -declare CTRL_NET="ctrl_net-$(shuf -i 0-99999999 -n 1)" -declare CTRL_NET_PREFIX=$(new_net_prefix) -declare TEST_NET="test_net-$(shuf -i 0-99999999 -n 1)" -declare TEST_NET_PREFIX=$(new_net_prefix) -declare -r tolerance_usecs=100000 -# On both DUT and test runner, testing packets are on the eth2 interface. -declare -r TEST_DEVICE="eth2" -# Number of bits in the *_NET_PREFIX variables. -declare -r NET_MASK="24" -# Last bits of the DUT's IP address. -declare -r DUT_NET_SUFFIX=".10" -# Control port. -declare -r CTRL_PORT="40000" -# Last bits of the test runner's IP address. -declare -r TEST_RUNNER_NET_SUFFIX=".20" -declare -r TIMEOUT="60" -declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetdrill" - -# Make sure that docker is installed. -docker --version - -function finish { - local cleanup_success=1 - for net in "${CTRL_NET}" "${TEST_NET}"; do - # Kill all processes attached to ${net}. - for docker_command in "kill" "rm"; do - (docker network inspect "${net}" \ - --format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \ - | xargs -r docker "${docker_command}") || \ - cleanup_success=0 - done - # Remove the network. - docker network rm "${net}" || \ - cleanup_success=0 - done - - if ((!$cleanup_success)); then - echo "FAIL: Cleanup command failed" - exit 4 - fi -} -trap finish EXIT - -# Subnet for control packets between test runner and DUT. -while ! docker network create \ - "--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do - sleep 0.1 - CTRL_NET_PREFIX=$(new_net_prefix) - CTRL_NET="ctrl_net-$(shuf -i 0-99999999 -n 1)" -done - -# Subnet for the packets that are part of the test. -while ! docker network create \ - "--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do - sleep 0.1 - TEST_NET_PREFIX=$(new_net_prefix) - TEST_NET="test_net-$(shuf -i 0-99999999 -n 1)" -done - -# Create the DUT container and connect to network. -DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \ - --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) -docker network connect "${CTRL_NET}" \ - --ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ - || (docker kill ${DUT}; docker rm ${DUT}; false) -docker network connect "${TEST_NET}" \ - --ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ - || (docker kill ${DUT}; docker rm ${DUT}; false) -docker start "${DUT}" - -# Create the test runner container and connect to network. -TEST_RUNNER=$(docker create --privileged --rm \ - --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) -docker network connect "${CTRL_NET}" \ - --ip "${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${TEST_RUNNER}" \ - || (docker kill ${TEST_RUNNER}; docker rm ${REST_RUNNER}; false) -docker network connect "${TEST_NET}" \ - --ip "${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${TEST_RUNNER}" \ - || (docker kill ${TEST_RUNNER}; docker rm ${REST_RUNNER}; false) -docker start "${TEST_RUNNER}" - -# Run tcpdump in the test runner unbuffered, without dns resolution, just on the -# interface with the test packets. -docker exec -t ${TEST_RUNNER} tcpdump -U -n -i "${TEST_DEVICE}" & - -# Start a packetdrill server on the test_runner. The packetdrill server sends -# packets and asserts that they are received. -docker exec -d "${TEST_RUNNER}" \ - ${PACKETDRILL} --wire_server --wire_server_dev="${TEST_DEVICE}" \ - --wire_server_ip="${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ - --wire_server_port="${CTRL_PORT}" \ - --local_ip="${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ - --remote_ip="${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" - -# Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will -# issue a RST. To prevent this IPtables can be used to filter those out. -docker exec "${TEST_RUNNER}" \ - iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP - -# Wait for the packetdrill server on the test runner to come. Attempt to -# connect to it from the DUT every 100 milliseconds until success. -while ! docker exec "${DUT}" \ - nc -zv "${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${CTRL_PORT}"; do - sleep 0.1 -done - -# Copy the packetdrill setup script to the DUT. -docker cp -L "${INIT_SCRIPT}" "${DUT}:packetdrill_setup.sh" - -# Copy the packetdrill scripts to the DUT. -declare -a dut_scripts -for script in $scripts; do - docker cp -L "${script}" "${DUT}:$(basename ${script})" - dut_scripts+=("/$(basename ${script})") -done - -# Start a packetdrill client on the DUT. The packetdrill client runs POSIX -# socket commands and also sends instructions to the server. -docker exec -t "${DUT}" \ - ${PACKETDRILL} --wire_client --wire_client_dev="${TEST_DEVICE}" \ - --wire_server_ip="${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ - --wire_server_port="${CTRL_PORT}" \ - --local_ip="${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" \ - --remote_ip="${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ - --init_scripts=/packetdrill_setup.sh \ - --tolerance_usecs="${tolerance_usecs}" "${dut_scripts[@]}" - -echo PASS: No errors. diff --git a/test/packetdrill/reset_for_ack_when_no_syn_cookies_in_use.pkt b/test/packetdrill/reset_for_ack_when_no_syn_cookies_in_use.pkt deleted file mode 100644 index a86b90ce6..000000000 --- a/test/packetdrill/reset_for_ack_when_no_syn_cookies_in_use.pkt +++ /dev/null @@ -1,9 +0,0 @@ -// Test that a listening socket generates a RST when it receives an -// ACK and syn cookies are not in use. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 bind(3, ..., ...) = 0 - -+0 listen(3, 1) = 0 -+0.1 < . 1:1(0) ack 1 win 32792 -+0 > R 1:1(0) ack 0 win 0
\ No newline at end of file diff --git a/test/packetdrill/sanity_test.pkt b/test/packetdrill/sanity_test.pkt deleted file mode 100644 index b3b58c366..000000000 --- a/test/packetdrill/sanity_test.pkt +++ /dev/null @@ -1,7 +0,0 @@ -// Basic sanity test. One system call. -// -// All of the plumbing has to be working however, and the packetdrill wire -// client needs to be able to connect to the wire server and send the script, -// probe local interfaces, run through the test w/ timings, etc. - -0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 diff --git a/test/packetdrill/tcp_defer_accept.pkt b/test/packetdrill/tcp_defer_accept.pkt deleted file mode 100644 index a17f946db..000000000 --- a/test/packetdrill/tcp_defer_accept.pkt +++ /dev/null @@ -1,48 +0,0 @@ -// Test that a bare ACK does not complete a connection when TCP_DEFER_ACCEPT -// timeout is not hit but an ACK w/ data does complete and deliver the -// connection to the accept queue. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 setsockopt(3, SOL_TCP, TCP_DEFER_ACCEPT, [5], 4) = 0 -+0.000 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 -+0 bind(3, ..., ...) = 0 - -// Set backlog to 1 so that we can easily test. -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0.0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0.0 > S. 0:0(0) ack 1 <...> - -// Send a bare ACK this should not complete the connection as we -// set the TCP_DEFER_ACCEPT above. -+0.0 < . 1:1(0) ack 1 win 257 - -// The bare ACK should be dropped and no connection should be delivered -// to the accept queue. -+0.100 accept(3, ..., ...) = -1 EWOULDBLOCK (operation would block) - -// Send another bare ACK and it should still fail we set TCP_DEFER_ACCEPT -// to 5 seconds above. -+2.5 < . 1:1(0) ack 1 win 257 -+0.100 accept(3, ..., ...) = -1 EWOULDBLOCK (operation would block) - -// set accept socket back to blocking. -+0.000 fcntl(3, F_SETFL, O_RDWR) = 0 - -// Now send an ACK w/ data. This should complete the connection -// and deliver the socket to the accept queue. -+0.1 < . 1:5(4) ack 1 win 257 -+0.0 > . 1:1(0) ack 5 <...> - -// This should cause connection to transition to connected state. -+0.000 accept(3, ..., ...) = 4 -+0.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 - -// Now read the data and we should get 4 bytes. -+0.000 read(4,..., 4) = 4 -+0.000 close(4) = 0 - -+0.0 > F. 1:1(0) ack 5 <...> -+0.0 < F. 5:5(0) ack 2 win 257 -+0.01 > . 2:2(0) ack 6 <...>
\ No newline at end of file diff --git a/test/packetdrill/tcp_defer_accept_timeout.pkt b/test/packetdrill/tcp_defer_accept_timeout.pkt deleted file mode 100644 index 201fdeb14..000000000 --- a/test/packetdrill/tcp_defer_accept_timeout.pkt +++ /dev/null @@ -1,48 +0,0 @@ -// Test that a bare ACK is accepted after TCP_DEFER_ACCEPT timeout -// is hit and a connection is delivered. - -0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 -+0 setsockopt(3, SOL_TCP, TCP_DEFER_ACCEPT, [3], 4) = 0 -+0.000 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 -+0 bind(3, ..., ...) = 0 - -// Set backlog to 1 so that we can easily test. -+0 listen(3, 1) = 0 - -// Establish a connection without timestamps. -+0.0 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7> -+0.0 > S. 0:0(0) ack 1 <...> - -// Send a bare ACK this should not complete the connection as we -// set the TCP_DEFER_ACCEPT above. -+0.0 < . 1:1(0) ack 1 win 257 - -// The bare ACK should be dropped and no connection should be delivered -// to the accept queue. -+0.100 accept(3, ..., ...) = -1 EWOULDBLOCK (operation would block) - -// Send another bare ACK and it should still fail we set TCP_DEFER_ACCEPT -// to 5 seconds above. -+2.5 < . 1:1(0) ack 1 win 257 -+0.100 accept(3, ..., ...) = -1 EWOULDBLOCK (operation would block) - -// set accept socket back to blocking. -+0.000 fcntl(3, F_SETFL, O_RDWR) = 0 - -// We should see one more retransmit of the SYN-ACK as a last ditch -// attempt when TCP_DEFER_ACCEPT timeout is hit to trigger another -// ACK or a packet with data. -+.35~+2.35 > S. 0:0(0) ack 1 <...> - -// Now send another bare ACK after TCP_DEFER_ACCEPT time has been passed. -+0.0 < . 1:1(0) ack 1 win 257 - -// The ACK above should cause connection to transition to connected state. -+0.000 accept(3, ..., ...) = 4 -+0.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 - -+0.000 close(4) = 0 - -+0.0 > F. 1:1(0) ack 1 <...> -+0.0 < F. 1:1(0) ack 2 win 257 -+0.01 > . 2:2(0) ack 2 <...> diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md deleted file mode 100644 index fe0976ba5..000000000 --- a/test/packetimpact/README.md +++ /dev/null @@ -1,709 +0,0 @@ -# Packetimpact - -## What is packetimpact? - -Packetimpact is a tool for platform-independent network testing. It is heavily -inspired by [packetdrill](https://github.com/google/packetdrill). It creates two -docker containers connected by a network. One is for the test bench, which -operates the test. The other is for the device-under-test (DUT), which is the -software being tested. The test bench communicates over the network with the DUT -to check correctness of the network. - -### Goals - -Packetimpact aims to provide: - -* A **multi-platform** solution that can test both Linux and gVisor. -* **Conciseness** on par with packetdrill scripts. -* **Control-flow** like for loops, conditionals, and variables. -* **Flexibilty** to specify every byte in a packet or use multiple sockets. - -## How to run packetimpact tests? - -Build the test container image by running the following at the root of the -repository: - -```bash -$ make load-packetimpact -``` - -Run a test, e.g. `fin_wait2_timeout`, against Linux: - -```bash -$ bazel test //test/packetimpact/tests:fin_wait2_timeout_native_test -``` - -Run the same test, but against gVisor: - -```bash -$ bazel test //test/packetimpact/tests:fin_wait2_timeout_netstack_test -``` - -## When to use packetimpact? - -There are a few ways to write networking tests for gVisor currently: - -* [Go unit tests](https://github.com/google/gvisor/tree/master/pkg/tcpip) -* [syscall tests](https://github.com/google/gvisor/tree/master/test/syscalls/linux) -* [packetdrill tests](https://github.com/google/gvisor/tree/master/test/packetdrill) -* packetimpact tests - -The right choice depends on the needs of the test. - -Feature | Go unit test | syscall test | packetdrill | packetimpact --------------- | ------------ | ------------ | ----------- | ------------ -Multi-platform | no | **YES** | **YES** | **YES** -Concise | no | somewhat | somewhat | **VERY** -Control-flow | **YES** | **YES** | no | **YES** -Flexible | **VERY** | no | somewhat | **VERY** - -### Go unit tests - -If the test depends on the internals of gVisor and doesn't need to run on Linux -or other platforms for comparison purposes, a Go unit test can be appropriate. -They can observe internals of gVisor networking. The downside is that they are -**not concise** and **not multi-platform**. If you require insight on gVisor -internals, this is the right choice. - -### Syscall tests - -Syscall tests are **multi-platform** but cannot examine the internals of gVisor -networking. They are **concise**. They can use **control-flow** structures like -conditionals, for loops, and variables. However, they are limited to only what -the POSIX interface provides so they are **not flexible**. For example, you -would have difficulty writing a syscall test that intentionally sends a bad IP -checksum. Or if you did write that test with raw sockets, it would be very -**verbose** to write a test that intentionally send wrong checksums, wrong -protocols, wrong sequence numbers, etc. - -### Packetdrill tests - -Packetdrill tests are **multi-platform** and can run against both Linux and -gVisor. They are **concise** and use a special packetdrill scripting language. -They are **more flexible** than a syscall test in that they can send packets -that a syscall test would have difficulty sending, like a packet with a -calcuated ACK number. But they are also somewhat limimted in flexibiilty in that -they can't do tests with multiple sockets. They have **no control-flow** ability -like variables or conditionals. For example, it isn't possible to send a packet -that depends on the window size of a previous packet because the packetdrill -language can't express that. Nor could you branch based on whether or not the -other side supports window scaling, for example. - -### Packetimpact tests - -Packetimpact tests are similar to Packetdrill tests except that they are written -in Go instead of the packetdrill scripting language. That gives them all the -**control-flow** abilities of Go (loops, functions, variables, etc). They are -**multi-platform** in the same way as packetdrill tests but even more -**flexible** because Go is more expressive than the scripting language of -packetdrill. However, Go is **not as concise** as the packetdrill language. Many -design decisions below are made to mitigate that. - -## How it works - -``` - Testbench Device-Under-Test (DUT) - +-------------------+ +------------------------+ - | | TEST NET | | - | rawsockets.go <-->| <===========> | <---+ | - | ^ | | | | - | | | | | | - | v | | | | - | unittest | | | | - | ^ | | | | - | | | | | | - | v | | v | - | dut.go <========gRPC========> posix server | - | | CONTROL NET | | - +-------------------+ +------------------------+ -``` - -Two docker containers are created by a "runner" script, one for the testbench -and the other for the device under test (DUT). The script connects the two -containers with a control network and test network. It also does some other -tasks like waiting until the DUT is ready before starting the test and disabling -Linux networking that would interfere with the test bench. - -### DUT - -The DUT container runs a program called the "posix_server". The posix_server is -written in c++ for maximum portability. It is compiled on the host. The script -that starts the containers copies it into the DUT's container and runs it. It's -job is to receive directions from the test bench on what actions to take. For -this, the posix_server does three steps in a loop: - -1. Listen for a request from the test bench. -2. Execute a command. -3. Send the response back to the test bench. - -The requests and responses are -[protobufs](https://developers.google.com/protocol-buffers) and the -communication is done with [gRPC](https://grpc.io/). The commands run are -[POSIX socket commands](https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions), -with the inputs and outputs converted into protobuf requests and responses. All -communication is on the control network, so that the test network is unaffected -by extra packets. - -For example, this is the request and response pair to call -[`socket()`](http://man7.org/linux/man-pages/man2/socket.2.html): - -```protocol-buffer -message SocketRequest { - int32 domain = 1; - int32 type = 2; - int32 protocol = 3; -} - -message SocketResponse { - int32 fd = 1; - int32 errno_ = 2; -} -``` - -##### Alternatives considered - -* We could have use JSON for communication instead. It would have been a - lighter-touch than protobuf but protobuf handles all the data type and has - strict typing to prevent a class of errors. The test bench could be written - in other languages, too. -* Instead of mimicking the POSIX interfaces, arguments could have had a more - natural form, like the `bind()` getting a string IP address instead of bytes - in a `sockaddr_t`. However, conforming to the existing structures keeps more - of the complexity in Go and keeps the posix_server simpler and thus more - likely to compile everywhere. - -### Test Bench - -The test bench does most of the work in a test. It is a Go program that compiles -on the host and is copied by the script into test bench's container. It is a -regular [go unit test](https://golang.org/pkg/testing/) that imports the test -bench framework. The test bench framwork is based on three basic utilities: - -* Commanding the DUT to run POSIX commands and return responses. -* Sending raw packets to the DUT on the test network. -* Listening for raw packets from the DUT on the test network. - -#### DUT commands - -To keep the interface to the DUT consistent and easy-to-use, each POSIX command -supported by the posix_server is wrapped in functions with signatures similar to -the ones in the [Go unix package](https://godoc.org/golang.org/x/sys/unix). This -way all the details of endianess and (un)marshalling of go structs such as -[unix.Timeval](https://godoc.org/golang.org/x/sys/unix#Timeval) is handled in -one place. This also makes it straight-forward to convert tests that use `unix.` -or `syscall.` calls to `dut.` calls. - -For example, creating a connection to the DUT and commanding it to make a socket -looks like this: - -```go -dut := testbench.NewDut(t) -fd, err := dut.SocketWithErrno(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP) -if fd < 0 { - t.Fatalf(...) -} -``` - -Because the usual case is to fail the test when the DUT fails to create a -socket, there is a concise version of each of the `...WithErrno` functions that -does that: - -```go -dut := testbench.NewDut(t) -fd := dut.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_IP) -``` - -The DUT and other structs in the code store a `*testing.T` so that they can -provide versions of functions that call `t.Fatalf(...)`. This helps keep tests -concise. - -##### Alternatives considered - -* Instead of mimicking the `unix.` go interface, we could have invented a more - natural one, like using `float64` instead of `Timeval`. However, using the - same function signatures that `unix.` has makes it easier to convert code to - `dut.`. Also, using an existing interface ensures that we don't invent an - interface that isn't extensible. For example, if we invented a function for - `bind()` that didn't support IPv6 and later we had to add a second `bind6()` - function. - -#### Sending/Receiving Raw Packets - -The framework wraps POSIX sockets for sending and receiving raw frames. Both -send and receive are synchronous commands. -[SO_RCVTIMEO](http://man7.org/linux/man-pages/man7/socket.7.html) is used to set -a timeout on the receive commands. For ease of use, these are wrapped in an -`Injector` and a `Sniffer`. They have functions: - -```go -func (s *Sniffer) Recv(timeout time.Duration) []byte {...} -func (i *Injector) Send(b []byte) {...} -``` - -##### Alternatives considered - -* [gopacket](https://github.com/google/gopacket) pcap has raw socket support - but requires cgo. cgo is not guaranteed to be portable from the host to the - container and in practice, the container doesn't recognize binaries built on - the host if they use cgo. -* Both gVisor and gopacket have the ability to read and write pcap files - without cgo but that is insufficient here because we can't just replay pcap - files, we need a more dynamic solution. -* The sniffer and injector can't share a socket because they need to be bound - differently. -* Sniffing could have been done asynchronously with channels, obviating the - need for `SO_RCVTIMEO`. But that would introduce asynchronous complication. - `SO_RCVTIMEO` is well supported on the test bench. - -#### `Layer` struct - -A large part of packetimpact tests is creating packets to send and comparing -received packets against expectations. To keep tests concise, it is useful to be -able to specify just the important parts of packets that need to be set. For -example, sending a packet with default values except for TCP Flags. And for -packets received, it's useful to be able to compare just the necessary parts of -received packets and ignore the rest. - -To aid in both of those, Go structs with optional fields are created for each -encapsulation type, such as IPv4, TCP, and Ethernet. This is inspired by -[scapy](https://scapy.readthedocs.io/en/latest/). For example, here is the -struct for Ethernet: - -```go -type Ether struct { - LayerBase - SrcAddr *tcpip.LinkAddress - DstAddr *tcpip.LinkAddress - Type *tcpip.NetworkProtocolNumber -} -``` - -Each struct has the same fields as those in the -[gVisor headers](https://github.com/google/gvisor/tree/master/pkg/tcpip/header) -but with a pointer for each field that may be `nil`. - -##### Alternatives considered - -* Just use []byte like gVisor headers do. The drawback is that it makes the - tests more verbose. - * For example, there would be no way to call `Send(myBytes)` concisely and - indicate if the checksum should be calculated automatically versus - overridden. The only way would be to add lines to the test to calculate - it before each Send, which is wordy. Or make multiple versions of Send: - one that checksums IP, one that doesn't, one that checksums TCP, one - that does both, etc. That would be many combinations. - * Filtering inputs would become verbose. Either: - * large conditionals that need to be repeated many places: - `h[FlagOffset] == SYN && h[LengthOffset:LengthOffset+2] == ...` or - * Many functions, one per field, like: `filterByFlag(myBytes, SYN)`, - `filterByLength(myBytes, 20)`, `filterByNextProto(myBytes, 0x8000)`, - etc. - * Using pointers allows us to combine `Layer`s with reflection. So the - default `Layers` can be overridden by a `Layers` with just the TCP - conection's src/dst which can be overridden by one with just a test - specific TCP window size. - * It's a proven way to separate the details of a packet from the byte - format as shown by scapy's success. -* Use packetgo. It's more general than parsing packets with gVisor. However: - * packetgo doesn't have optional fields so many of the above problems - still apply. - * It would be yet another dependency. - * It's not as well known to engineers that are already writing gVisor - code. - * It might be a good candidate for replacing the parsing of packets into - `Layer`s if all that parsing turns out to be more work than parsing by - packetgo and converting *that* to `Layer`. packetgo has easier to use - getters for the layers. This could be done later in a way that doesn't - break tests. - -#### `Layer` methods - -The `Layer` structs provide a way to partially specify an encapsulation. They -also need methods for using those partially specified encapsulation, for example -to marshal them to bytes or compare them. For those, each encapsulation -implements the `Layer` interface: - -```go -// Layer is the interface that all encapsulations must implement. -// -// A Layer is an encapsulation in a packet, such as TCP, IPv4, IPv6, etc. A -// Layer contains all the fields of the encapsulation. Each field is a pointer -// and may be nil. -type Layer interface { - // toBytes converts the Layer into bytes. In places where the Layer's field - // isn't nil, the value that is pointed to is used. When the field is nil, a - // reasonable default for the Layer is used. For example, "64" for IPv4 TTL - // and a calculated checksum for TCP or IP. Some layers require information - // from the previous or next layers in order to compute a default, such as - // TCP's checksum or Ethernet's type, so each Layer has a doubly-linked list - // to the layer's neighbors. - toBytes() ([]byte, error) - - // match checks if the current Layer matches the provided Layer. If either - // Layer has a nil in a given field, that field is considered matching. - // Otherwise, the values pointed to by the fields must match. - match(Layer) bool - - // length in bytes of the current encapsulation - length() int - - // next gets a pointer to the encapsulated Layer. - next() Layer - - // prev gets a pointer to the Layer encapsulating this one. - prev() Layer - - // setNext sets the pointer to the encapsulated Layer. - setNext(Layer) - - // setPrev sets the pointer to the Layer encapsulating this one. - setPrev(Layer) -} -``` - -The `next` and `prev` make up a link listed so that each layer can get at the -information in the layer around it. This is necessary for some protocols, like -TCP that needs the layer before and payload after to compute the checksum. Any -sequence of `Layer` structs is valid so long as the parser and `toBytes` -functions can map from type to protool number and vice-versa. When the mapping -fails, an error is emitted explaining what functionality is missing. The -solution is either to fix the ordering or implement the missing protocol. - -For each `Layer` there is also a parsing function. For example, this one is for -Ethernet: - -``` -func ParseEther(b []byte) (Layers, error) -``` - -The parsing function converts bytes received on the wire into a `Layer` -(actually `Layers`, see below) which has no `nil`s in it. By using -`match(Layer)` to compare against another `Layer` that *does* have `nil`s in it, -the received bytes can be partially compared. The `nil`s behave as -"don't-cares". - -##### Alternatives considered - -* Matching against `[]byte` instead of converting to `Layer` first. - * The downside is that it precludes the use of a `cmp.Equal` one-liner to - do comparisons. - * It creates confusion in the code to deal with both representations at - different times. For example, is the checksum calculated on `[]byte` or - `Layer` when sending? What about when checking received packets? - -#### `Layers` - -``` -type Layers []Layer - -func (ls *Layers) match(other Layers) bool {...} -func (ls *Layers) toBytes() ([]byte, error) {...} -``` - -`Layers` is an array of `Layer`. It represents a stack of encapsulations, such -as `Layers{Ether{},IPv4{},TCP{},Payload{}}`. It also has `toBytes()` and -`match(Layers)`, like `Layer`. The parse functions above actually return -`Layers` and not `Layer` because they know about the headers below and -sequentially call each parser on the remaining, encapsulated bytes. - -All this leads to the ability to write concise packet processing. For example: - -```go -etherType := 0x8000 -flags = uint8(header.TCPFlagSyn|header.TCPFlagAck) -toMatch := Layers{Ether{Type: ðerType}, IPv4{}, TCP{Flags: &flags}} -for { - recvBytes := sniffer.Recv(time.Second) - if recvBytes == nil { - println("Got no packet for 1 second") - } - gotPacket, err := ParseEther(recvBytes) - if err == nil && toMatch.match(gotPacket) { - println("Got a TCP/IPv4/Eth packet with SYNACK") - } -} -``` - -##### Alternatives considered - -* Don't use previous and next pointers. - * Each layer may need to be able to interrogate the layers around it, like - for computing the next protocol number or total length. So *some* - mechanism is needed for a `Layer` to see neighboring layers. - * We could pass the entire array `Layers` to the `toBytes()` function. - Passing an array to a method that includes in the array the function - receiver itself seems wrong. - -#### `layerState` - -`Layers` represents the different headers of a packet but a connection includes -more state. For example, a TCP connection needs to keep track of the next -expected sequence number and also the next sequence number to send. This is -stored in a `layerState` struct. This is the `layerState` for TCP: - -```go -// tcpState maintains state about a TCP connection. -type tcpState struct { - out, in TCP - localSeqNum, remoteSeqNum *seqnum.Value - synAck *TCP - portPickerFD int - finSent bool -} -``` - -The next sequence numbers for each side of the connection are stored. `out` and -`in` have defaults for the TCP header, such as the expected source and -destination ports for outgoing packets and incoming packets. - -##### `layerState` interface - -```go -// layerState stores the state of a layer of a connection. -type layerState interface { - // outgoing returns an outgoing layer to be sent in a frame. - outgoing() Layer - - // incoming creates an expected Layer for comparing against a received Layer. - // Because the expectation can depend on values in the received Layer, it is - // an input to incoming. For example, the ACK number needs to be checked in a - // TCP packet but only if the ACK flag is set in the received packet. - incoming(received Layer) Layer - - // sent updates the layerState based on the Layer that was sent. The input is - // a Layer with all prev and next pointers populated so that the entire frame - // as it was sent is available. - sent(sent Layer) error - - // received updates the layerState based on a Layer that is receieved. The - // input is a Layer with all prev and next pointers populated so that the - // entire frame as it was receieved is available. - received(received Layer) error - - // close frees associated resources held by the LayerState. - close() error -} -``` - -`outgoing` generates the default Layer for an outgoing packet. For TCP, this -would be a `TCP` with the source and destination ports populated. Because they -are static, they are stored inside the `out` member of `tcpState`. However, the -sequence numbers change frequently so the outgoing sequence number is stored in -the `localSeqNum` and put into the output of outgoing for each call. - -`incoming` does the same functions for packets that arrive but instead of -generating a packet to send, it generates an expect packet for filtering packets -that arrive. For example, if a `TCP` header arrives with the wrong ports, it can -be ignored as belonging to a different connection. `incoming` needs the received -header itself as an input because the filter may depend on the input. For -example, the expected sequence number depends on the flags in the TCP header. - -`sent` and `received` are run for each header that is actually sent or received -and used to update the internal state. `incoming` and `outgoing` should *not* be -used for these purpose. For example, `incoming` is called on every packet that -arrives but only packets that match ought to actually update the state. -`outgoing` is called to created outgoing packets and those packets are always -sent, so unlike `incoming`/`received`, there is one `outgoing` call for each -`sent` call. - -`close` cleans up after the layerState. For example, TCP and UDP need to keep a -port reserved and then release it. - -#### Connections - -Using `layerState` above, we can create connections. - -```go -// Connection holds a collection of layer states for maintaining a connection -// along with sockets for sniffer and injecting packets. -type Connection struct { - layerStates []layerState - injector Injector - sniffer Sniffer - t *testing.T -} -``` - -The connection stores an array of `layerState` in the order that the headers -should be present in the frame to send. For example, Ether then IPv4 then TCP. -The injector and sniffer are for writing and reading frames. A `*testing.T` is -stored so that internal errors can be reported directly without code in the unit -test. - -The `Connection` has some useful functions: - -```go -// Close frees associated resources held by the Connection. -func (conn *Connection) Close() {...} -// CreateFrame builds a frame for the connection with layer overriding defaults -// of the innermost layer and additionalLayers added after it. -func (conn *Connection) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {...} -// SendFrame sends a frame on the wire and updates the state of all layers. -func (conn *Connection) SendFrame(frame Layers) {...} -// Send a packet with reasonable defaults. Potentially override the final layer -// in the connection with the provided layer and add additionLayers. -func (conn *Connection) Send(layer Layer, additionalLayers ...Layer) {...} -// Expect a frame with the final layerStates layer matching the provided Layer -// within the timeout specified. If it doesn't arrive in time, it returns nil. -func (conn *Connection) Expect(layer Layer, timeout time.Duration) (Layer, error) {...} -// ExpectFrame expects a frame that matches the provided Layers within the -// timeout specified. If it doesn't arrive in time, it returns nil. -func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layers, error) {...} -// Drain drains the sniffer's receive buffer by receiving packets until there's -// nothing else to receive. -func (conn *Connection) Drain() {...} -``` - -`CreateFrame` uses the `[]layerState` to create a frame to send. The first -argument is for overriding defaults in the last header of the frame, because -this is the most common need. For a TCPIPv4 connection, this would be the TCP -header. Optional additionalLayers can be specified to add to the frame being -created, such as a `Payload` for `TCP`. - -`SendFrame` sends the frame to the DUT. It is combined with `CreateFrame` to -make `Send`. For unittests with basic sending needs, `Send` can be used. If more -control is needed over the frame, it can be made with `CreateFrame`, modified in -the unit test, and then sent with `SendFrame`. - -On the receiving side, there is `Expect` and `ExpectFrame`. Like with the -sending side, there are two forms of each function, one for just the last header -and one for the whole frame. The expect functions use the `[]layerState` to -create a template for the expected incoming frame. That frame is then overridden -by the values in the first argument. Finally, a loop starts sniffing packets on -the wire for frames. If a matching frame is found before the timeout, it is -returned without error. If not, nil is returned and the error contains text of -all the received frames that didn't match. Exactly one of the outputs will be -non-nil, even if no frames are received at all. - -`Drain` sniffs and discards all the frames that have yet to be received. A -common way to write a test is: - -```go -conn.Drain() // Discard all outstanding frames. -conn.Send(...) // Send a frame with overrides. -// Now expect a frame with a certain header and fail if it doesn't arrive. -if _, err := conn.Expect(...); err != nil { t.Fatal(...) } -``` - -Or for a test where we want to check that no frame arrives: - -```go -if gotOne, _ := conn.Expect(...); gotOne != nil { t.Fatal(...) } -``` - -#### Specializing `Connection` - -Because there are some common combinations of `layerState` into `Connection`, -they are defined: - -```go -// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection. -type TCPIPv4 Connection -// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection. -type UDPIPv4 Connection -``` - -Each has a `NewXxx` function to create a new connection with reasonable -defaults. They also have functions that call the underlying `Connection` -functions but with specialization and tighter type-checking. For example: - -```go -func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) { - (*Connection)(conn).Send(&tcp, additionalLayers...) -} -func (conn *TCPIPv4) Drain() { - conn.sniffer.Drain() -} -``` - -They may also have some accessors to get or set the internal state of the -connection: - -```go -func (conn *TCPIPv4) state() *tcpState { - state, ok := conn.layerStates[len(conn.layerStates)-1].(*tcpState) - if !ok { - conn.t.Fatalf("expected final state of %v to be tcpState", conn.layerStates) - } - return state -} -func (conn *TCPIPv4) RemoteSeqNum() *seqnum.Value { - return conn.state().remoteSeqNum -} -func (conn *TCPIPv4) LocalSeqNum() *seqnum.Value { - return conn.state().localSeqNum -} -``` - -Unittests will in practice use these functions and not the functions on -`Connection`. For example, `NewTCPIPv4()` and then call `Send` on that rather -than cast is to a `Connection` and call `Send` on that cast result. - -##### Alternatives considered - -* Instead of storing `outgoing` and `incoming`, store values. - * There would be many more things to store instead, like `localMac`, - `remoteMac`, `localIP`, `remoteIP`, `localPort`, and `remotePort`. - * Construction of a packet would be many lines to copy each of these - values into a `[]byte`. And there would be slight variations needed for - each encapsulation stack, like TCPIPv6 and ARP. - * Filtering incoming packets would be a long sequence: - * Compare the MACs, then - * Parse the next header, then - * Compare the IPs, then - * Parse the next header, then - * Compare the TCP ports. Instead it's all just one call to - `cmp.Equal(...)`, for all sequences. - * A TCPIPv6 connection could share most of the code. Only the type of the - IP addresses are different. The types of `outgoing` and `incoming` would - be remain `Layers`. - * An ARP connection could share all the Ethernet parts. The IP `Layer` - could be factored out of `outgoing`. After that, the IPv4 and IPv6 - connections could implement one interface and a single TCP struct could - have either network protocol through composition. - -## Putting it all together - -Here's what te start of a packetimpact unit test looks like. This test creates a -TCP connection with the DUT. There are added comments for explanation in this -document but a real test might not include them in order to stay even more -concise. - -```go -func TestMyTcpTest(t *testing.T) { - // Prepare a DUT for communication. - dut := testbench.NewDUT(t) - - // This does: - // dut.Socket() - // dut.Bind() - // dut.Getsockname() to learn the new port number - // dut.Listen() - listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(listenFD) // Tell the DUT to close the socket at the end of the test. - - // Monitor a new TCP connection with sniffer, injector, sequence number tracking, - // and reasonable outgoing and incoming packet field default IPs, MACs, and port numbers. - conn := testbench.NewTCPIPv4(t, dut, remotePort) - - // Perform a 3-way handshake: send SYN, expect SYNACK, send ACK. - conn.Handshake() - - // Tell the DUT to accept the new connection. - acceptFD := dut.Accept(acceptFd) -} -``` - -### Adding a new packetimpact test - -* Create a go test in the [tests directory](tests/) -* Add a `packetimpact_testbench` rule in [BUILD](tests/BUILD) -* Add the test into the `ALL_TESTS` list in [defs.bzl](runner/defs.bzl), - otherwise you will see an error message complaining about a missing test. - -## Other notes - -* The time between receiving a SYN-ACK and replying with an ACK in `Handshake` - is about 3ms. This is much slower than the native unix response, which is - about 0.3ms. Packetdrill gets closer to 0.3ms. For tests where timing is - crucial, packetdrill is faster and more precise. diff --git a/test/packetimpact/dut/BUILD b/test/packetimpact/dut/BUILD deleted file mode 100644 index 0be14ca3e..000000000 --- a/test/packetimpact/dut/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "grpcpp") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -cc_binary( - name = "posix_server", - srcs = ["posix_server.cc"], - linkstatic = 1, - static = True, # This is needed for running in a docker container. - deps = [ - grpcpp, - "//test/packetimpact/proto:posix_server_cc_grpc_proto", - "//test/packetimpact/proto:posix_server_cc_proto", - "@com_google_absl//absl/strings:str_format", - ], -) - -cc_binary( - name = "posix_server_dynamic", - srcs = ["posix_server.cc"], - deps = [ - grpcpp, - "//test/packetimpact/proto:posix_server_cc_grpc_proto", - "//test/packetimpact/proto:posix_server_cc_proto", - "@com_google_absl//absl/strings:str_format", - ], -) diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc deleted file mode 100644 index ea83bbe72..000000000 --- a/test/packetimpact/dut/posix_server.cc +++ /dev/null @@ -1,464 +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. - -#include <arpa/inet.h> -#include <fcntl.h> -#include <getopt.h> -#include <netdb.h> -#include <netinet/in.h> -#include <poll.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> - -#include <iostream> -#include <unordered_map> - -#include "include/grpcpp/security/server_credentials.h" -#include "include/grpcpp/server_builder.h" -#include "include/grpcpp/server_context.h" -#include "absl/strings/str_format.h" -#include "test/packetimpact/proto/posix_server.grpc.pb.h" -#include "test/packetimpact/proto/posix_server.pb.h" - -// Converts a sockaddr_storage to a Sockaddr message. -::grpc::Status sockaddr_to_proto(const sockaddr_storage &addr, - socklen_t addrlen, - posix_server::Sockaddr *sockaddr_proto) { - switch (addr.ss_family) { - case AF_INET: { - auto addr_in = reinterpret_cast<const sockaddr_in *>(&addr); - auto response_in = sockaddr_proto->mutable_in(); - response_in->set_family(addr_in->sin_family); - response_in->set_port(ntohs(addr_in->sin_port)); - response_in->mutable_addr()->assign( - reinterpret_cast<const char *>(&addr_in->sin_addr.s_addr), 4); - return ::grpc::Status::OK; - } - case AF_INET6: { - auto addr_in6 = reinterpret_cast<const sockaddr_in6 *>(&addr); - auto response_in6 = sockaddr_proto->mutable_in6(); - response_in6->set_family(addr_in6->sin6_family); - response_in6->set_port(ntohs(addr_in6->sin6_port)); - response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo)); - response_in6->mutable_addr()->assign( - reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16); - // sin6_scope_id is stored in host byte order. - // - // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html - response_in6->set_scope_id(addr_in6->sin6_scope_id); - return ::grpc::Status::OK; - } - } - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown Sockaddr"); -} - -::grpc::Status proto_to_sockaddr(const posix_server::Sockaddr &sockaddr_proto, - sockaddr_storage *addr, socklen_t *addr_len) { - switch (sockaddr_proto.sockaddr_case()) { - case posix_server::Sockaddr::SockaddrCase::kIn: { - auto proto_in = sockaddr_proto.in(); - if (proto_in.addr().size() != 4) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "IPv4 address must be 4 bytes"); - } - auto addr_in = reinterpret_cast<sockaddr_in *>(addr); - addr_in->sin_family = proto_in.family(); - addr_in->sin_port = htons(proto_in.port()); - proto_in.addr().copy(reinterpret_cast<char *>(&addr_in->sin_addr.s_addr), - 4); - *addr_len = sizeof(*addr_in); - break; - } - case posix_server::Sockaddr::SockaddrCase::kIn6: { - auto proto_in6 = sockaddr_proto.in6(); - if (proto_in6.addr().size() != 16) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "IPv6 address must be 16 bytes"); - } - auto addr_in6 = reinterpret_cast<sockaddr_in6 *>(addr); - addr_in6->sin6_family = proto_in6.family(); - addr_in6->sin6_port = htons(proto_in6.port()); - addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo()); - proto_in6.addr().copy( - reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16); - // sin6_scope_id is stored in host byte order. - // - // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html - addr_in6->sin6_scope_id = proto_in6.scope_id(); - *addr_len = sizeof(*addr_in6); - break; - } - case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET: - default: - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Unknown Sockaddr"); - } - return ::grpc::Status::OK; -} - -class PosixImpl final : public posix_server::Posix::Service { - ::grpc::Status Accept(grpc::ServerContext *context, - const ::posix_server::AcceptRequest *request, - ::posix_server::AcceptResponse *response) override { - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - response->set_fd(accept(request->sockfd(), - reinterpret_cast<sockaddr *>(&addr), &addrlen)); - if (response->fd() < 0) { - response->set_errno_(errno); - } - return sockaddr_to_proto(addr, addrlen, response->mutable_addr()); - } - - ::grpc::Status Bind(grpc::ServerContext *context, - const ::posix_server::BindRequest *request, - ::posix_server::BindResponse *response) override { - if (!request->has_addr()) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Missing address"); - } - - sockaddr_storage addr; - socklen_t addr_len; - auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len); - if (!err.ok()) { - return err; - } - - response->set_ret( - bind(request->sockfd(), reinterpret_cast<sockaddr *>(&addr), addr_len)); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Close(grpc::ServerContext *context, - const ::posix_server::CloseRequest *request, - ::posix_server::CloseResponse *response) override { - response->set_ret(close(request->fd())); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Connect(grpc::ServerContext *context, - const ::posix_server::ConnectRequest *request, - ::posix_server::ConnectResponse *response) override { - if (!request->has_addr()) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Missing address"); - } - sockaddr_storage addr; - socklen_t addr_len; - auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len); - if (!err.ok()) { - return err; - } - - response->set_ret(connect(request->sockfd(), - reinterpret_cast<sockaddr *>(&addr), addr_len)); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status GetSockName( - grpc::ServerContext *context, - const ::posix_server::GetSockNameRequest *request, - ::posix_server::GetSockNameResponse *response) override { - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - response->set_ret(getsockname( - request->sockfd(), reinterpret_cast<sockaddr *>(&addr), &addrlen)); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return sockaddr_to_proto(addr, addrlen, response->mutable_addr()); - } - - ::grpc::Status GetSockOpt( - grpc::ServerContext *context, - const ::posix_server::GetSockOptRequest *request, - ::posix_server::GetSockOptResponse *response) override { - switch (request->type()) { - case ::posix_server::GetSockOptRequest::BYTES: { - socklen_t optlen = request->optlen(); - std::vector<char> buf(optlen); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), buf.data(), - &optlen)); - if (optlen >= 0) { - response->mutable_optval()->set_bytesval(buf.data(), optlen); - } - break; - } - case ::posix_server::GetSockOptRequest::INT: { - int intval = 0; - socklen_t optlen = sizeof(intval); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), &intval, &optlen)); - response->mutable_optval()->set_intval(intval); - break; - } - case ::posix_server::GetSockOptRequest::TIME: { - timeval tv; - socklen_t optlen = sizeof(tv); - response->set_ret(::getsockopt(request->sockfd(), request->level(), - request->optname(), &tv, &optlen)); - response->mutable_optval()->mutable_timeval()->set_seconds(tv.tv_sec); - response->mutable_optval()->mutable_timeval()->set_microseconds( - tv.tv_usec); - break; - } - default: - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Unknown SockOpt Type"); - } - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Listen(grpc::ServerContext *context, - const ::posix_server::ListenRequest *request, - ::posix_server::ListenResponse *response) override { - response->set_ret(listen(request->sockfd(), request->backlog())); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Poll(::grpc::ServerContext *context, - const ::posix_server::PollRequest *request, - ::posix_server::PollResponse *response) override { - std::vector<struct pollfd> pfds; - pfds.reserve(request->pfds_size()); - for (const auto &pfd : request->pfds()) { - pfds.push_back({ - .fd = pfd.fd(), - .events = static_cast<short>(pfd.events()), - }); - } - int ret = ::poll(pfds.data(), pfds.size(), request->timeout_millis()); - - response->set_ret(ret); - if (ret < 0) { - response->set_errno_(errno); - } else { - // Only pollfds that have non-empty revents are returned, the client can't - // rely on indexes of the request array. - for (const auto &pfd : pfds) { - if (pfd.revents) { - auto *proto_pfd = response->add_pfds(); - proto_pfd->set_fd(pfd.fd); - proto_pfd->set_events(pfd.revents); - } - } - if (int ready = response->pfds_size(); ret != ready) { - return ::grpc::Status( - ::grpc::StatusCode::INTERNAL, - absl::StrFormat( - "poll's return value(%d) doesn't match the number of " - "file descriptors that are actually ready(%d)", - ret, ready)); - } - } - return ::grpc::Status::OK; - } - - ::grpc::Status Send(::grpc::ServerContext *context, - const ::posix_server::SendRequest *request, - ::posix_server::SendResponse *response) override { - response->set_ret(::send(request->sockfd(), request->buf().data(), - request->buf().size(), request->flags())); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status SendTo(::grpc::ServerContext *context, - const ::posix_server::SendToRequest *request, - ::posix_server::SendToResponse *response) override { - if (!request->has_dest_addr()) { - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Missing address"); - } - sockaddr_storage addr; - socklen_t addr_len; - auto err = proto_to_sockaddr(request->dest_addr(), &addr, &addr_len); - if (!err.ok()) { - return err; - } - - response->set_ret(::sendto(request->sockfd(), request->buf().data(), - request->buf().size(), request->flags(), - reinterpret_cast<sockaddr *>(&addr), addr_len)); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status SetNonblocking( - grpc::ServerContext *context, - const ::posix_server::SetNonblockingRequest *request, - ::posix_server::SetNonblockingResponse *response) override { - int flags = fcntl(request->fd(), F_GETFL); - if (flags == -1) { - response->set_ret(-1); - response->set_errno_(errno); - response->set_cmd("F_GETFL"); - return ::grpc::Status::OK; - } - if (request->nonblocking()) { - flags |= O_NONBLOCK; - } else { - flags &= ~O_NONBLOCK; - } - int ret = fcntl(request->fd(), F_SETFL, flags); - response->set_ret(ret); - if (ret == -1) { - response->set_errno_(errno); - response->set_cmd("F_SETFL"); - } - return ::grpc::Status::OK; - } - - ::grpc::Status SetSockOpt( - grpc::ServerContext *context, - const ::posix_server::SetSockOptRequest *request, - ::posix_server::SetSockOptResponse *response) override { - switch (request->optval().val_case()) { - case ::posix_server::SockOptVal::kBytesval: - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), - request->optval().bytesval().c_str(), - request->optval().bytesval().size())); - break; - case ::posix_server::SockOptVal::kIntval: { - int opt = request->optval().intval(); - response->set_ret(::setsockopt(request->sockfd(), request->level(), - request->optname(), &opt, sizeof(opt))); - break; - } - case ::posix_server::SockOptVal::kTimeval: { - timeval tv = {.tv_sec = static_cast<time_t>( - request->optval().timeval().seconds()), - .tv_usec = static_cast<suseconds_t>( - request->optval().timeval().microseconds())}; - response->set_ret(setsockopt(request->sockfd(), request->level(), - request->optname(), &tv, sizeof(tv))); - break; - } - default: - return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "Unknown SockOpt Type"); - } - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Socket(grpc::ServerContext *context, - const ::posix_server::SocketRequest *request, - ::posix_server::SocketResponse *response) override { - response->set_fd( - socket(request->domain(), request->type(), request->protocol())); - if (response->fd() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Shutdown(grpc::ServerContext *context, - const ::posix_server::ShutdownRequest *request, - ::posix_server::ShutdownResponse *response) override { - response->set_ret(shutdown(request->fd(), request->how())); - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } - - ::grpc::Status Recv(::grpc::ServerContext *context, - const ::posix_server::RecvRequest *request, - ::posix_server::RecvResponse *response) override { - std::vector<char> buf(request->len()); - response->set_ret( - recv(request->sockfd(), buf.data(), buf.size(), request->flags())); - if (response->ret() >= 0) { - response->set_buf(buf.data(), response->ret()); - } - if (response->ret() < 0) { - response->set_errno_(errno); - } - return ::grpc::Status::OK; - } -}; - -// Parse command line options. Returns a pointer to the first argument beyond -// the options. -void parse_command_line_options(int argc, char *argv[], std::string *ip, - int *port) { - static struct option options[] = {{"ip", required_argument, NULL, 1}, - {"port", required_argument, NULL, 2}, - {0, 0, 0, 0}}; - - // Parse the arguments. - int c; - while ((c = getopt_long(argc, argv, "", options, NULL)) > 0) { - if (c == 1) { - *ip = optarg; - } else if (c == 2) { - *port = std::stoi(std::string(optarg)); - } - } -} - -void run_server(const std::string &ip, int port) { - PosixImpl posix_service; - grpc::ServerBuilder builder; - std::string server_address = ip + ":" + std::to_string(port); - // Set the authentication mechanism. - std::shared_ptr<grpc::ServerCredentials> creds = - grpc::InsecureServerCredentials(); - builder.AddListeningPort(server_address, creds); - builder.RegisterService(&posix_service); - - std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); - std::cerr << "Server listening on " << server_address << std::endl; - server->Wait(); - std::cerr << "posix_server is finished." << std::endl; -} - -int main(int argc, char *argv[]) { - std::cerr << "posix_server is starting." << std::endl; - std::string ip; - int port; - parse_command_line_options(argc, argv, &ip, &port); - - std::cerr << "Got IP " << ip << " and port " << port << "." << std::endl; - run_server(ip, port); -} diff --git a/test/packetimpact/netdevs/BUILD b/test/packetimpact/netdevs/BUILD deleted file mode 100644 index 8d1193fed..000000000 --- a/test/packetimpact/netdevs/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package( - licenses = ["notice"], -) - -go_library( - name = "netdevs", - srcs = ["netdevs.go"], - visibility = ["//test/packetimpact:__subpackages__"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - ], -) - -go_test( - name = "netdevs_test", - size = "small", - srcs = ["netdevs_test.go"], - library = ":netdevs", - deps = ["@com_github_google_go_cmp//cmp:go_default_library"], -) diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go deleted file mode 100644 index 25dcfbf60..000000000 --- a/test/packetimpact/netdevs/netdevs.go +++ /dev/null @@ -1,122 +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 netdevs contains utilities for working with network devices. -package netdevs - -import ( - "fmt" - "net" - "regexp" - "strconv" - "strings" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -// A DeviceInfo represents a network device. -type DeviceInfo struct { - ID uint32 - MAC net.HardwareAddr - IPv4Addr net.IP - IPv4Net *net.IPNet - IPv6Addr net.IP - IPv6Net *net.IPNet -} - -var ( - deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`) - linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`) - inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`) - inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-F:/]+)`) -) - -// ParseDevicesWithRegex will parse the output with the given regexps to produce -// a map from device name to device information. It is assumed that deviceLine -// contains both a name and an ID. -func ParseDevicesWithRegex(cmdOutput string, deviceLine, linkLine, inetLine, inet6Line *regexp.Regexp) (map[string]DeviceInfo, error) { - var currentDevice string - var currentInfo DeviceInfo - deviceInfos := make(map[string]DeviceInfo) - for _, line := range strings.Split(cmdOutput, "\n") { - if m := deviceLine.FindStringSubmatch(line); m != nil { - if currentDevice != "" { - deviceInfos[currentDevice] = currentInfo - } - id, err := strconv.ParseUint(m[1], 10, 32) - if err != nil { - return nil, fmt.Errorf("parsing device ID %s: %w", m[1], err) - } - currentInfo = DeviceInfo{ID: uint32(id)} - currentDevice = m[2] - } else if m := linkLine.FindStringSubmatch(line); m != nil { - mac, err := net.ParseMAC(m[1]) - if err != nil { - return nil, err - } - currentInfo.MAC = mac - } else if m := inetLine.FindStringSubmatch(line); m != nil { - ipv4Addr, ipv4Net, err := net.ParseCIDR(m[1]) - if err != nil { - return nil, err - } - currentInfo.IPv4Addr = ipv4Addr - currentInfo.IPv4Net = ipv4Net - } else if m := inet6Line.FindStringSubmatch(line); m != nil { - ipv6Addr, ipv6Net, err := net.ParseCIDR(m[1]) - if err != nil { - return nil, err - } - currentInfo.IPv6Addr = ipv6Addr - currentInfo.IPv6Net = ipv6Net - } - } - if currentDevice != "" { - deviceInfos[currentDevice] = currentInfo - } - return deviceInfos, nil -} - -// ParseDevices parses the output from `ip addr show` into a map from device -// name to information about the device. -// -// Note: if multiple IPv6 addresses are assigned to a device, the last address -// displayed by `ip addr show` will be used. This is fine for packetimpact -// because we will always only have at most one IPv6 address assigned to each -// device. -func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) { - return ParseDevicesWithRegex(cmdOutput, deviceLine, linkLine, inetLine, inet6Line) -} - -// MACToIP converts the MAC address to an IPv6 link local address as described -// in RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20 -func MACToIP(mac net.HardwareAddr) net.IP { - addr := make([]byte, header.IPv6AddressSize) - addr[0] = 0xfe - addr[1] = 0x80 - header.EthernetAdddressToModifiedEUI64IntoBuf(tcpip.LinkAddress(mac), addr[8:]) - return net.IP(addr) -} - -// FindDeviceByIP finds a DeviceInfo and device name from an IP address in the -// output of ParseDevices. -func FindDeviceByIP(ip net.IP, devices map[string]DeviceInfo) (string, DeviceInfo, error) { - for dev, info := range devices { - if info.IPv4Addr.Equal(ip) { - return dev, info, nil - } - } - return "", DeviceInfo{}, fmt.Errorf("can't find %s on any interface", ip) -} diff --git a/test/packetimpact/netdevs/netdevs_test.go b/test/packetimpact/netdevs/netdevs_test.go deleted file mode 100644 index 379386980..000000000 --- a/test/packetimpact/netdevs/netdevs_test.go +++ /dev/null @@ -1,227 +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 netdevs - -import ( - "fmt" - "net" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func mustParseMAC(s string) net.HardwareAddr { - mac, err := net.ParseMAC(s) - if err != nil { - panic(fmt.Sprintf("failed to parse test MAC %q: %s", s, err)) - } - return mac -} - -func TestParseDevices(t *testing.T) { - for _, v := range []struct { - desc string - cmdOutput string - want map[string]DeviceInfo - }{ - { - desc: "v4 and v6", - cmdOutput: ` -1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 - link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 - inet 127.0.0.1/8 scope host lo - valid_lft forever preferred_lft forever - inet6 ::1/128 scope host - valid_lft forever preferred_lft forever -2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0 - valid_lft forever preferred_lft forever - inet6 fe80::42:c0ff:fea8:902/64 scope link tentative - valid_lft forever preferred_lft forever -2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 223.245.225.10/24 brd 223.245.225.255 scope global eth2 - valid_lft forever preferred_lft forever - inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative - valid_lft forever preferred_lft forever -2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1 - valid_lft forever preferred_lft forever - inet6 fe80::42:daff:fe33:130a/64 scope link tentative - valid_lft forever preferred_lft forever`, - want: map[string]DeviceInfo{ - "lo": { - ID: 1, - MAC: mustParseMAC("00:00:00:00:00:00"), - IPv4Addr: net.IPv4(127, 0, 0, 1), - IPv4Net: &net.IPNet{ - IP: net.IPv4(127, 0, 0, 0), - Mask: net.CIDRMask(8, 32), - }, - IPv6Addr: net.ParseIP("::1"), - IPv6Net: &net.IPNet{ - IP: net.ParseIP("::1"), - Mask: net.CIDRMask(128, 128), - }, - }, - "eth0": { - ID: 2613, - MAC: mustParseMAC("02:42:c0:a8:09:02"), - IPv4Addr: net.IPv4(192, 168, 9, 2), - IPv4Net: &net.IPNet{ - IP: net.IPv4(192, 168, 9, 0), - Mask: net.CIDRMask(24, 32), - }, - IPv6Addr: net.ParseIP("fe80::42:c0ff:fea8:902"), - IPv6Net: &net.IPNet{ - IP: net.ParseIP("fe80::"), - Mask: net.CIDRMask(64, 128), - }, - }, - "eth1": { - ID: 2617, - MAC: mustParseMAC("02:42:da:33:13:0a"), - IPv4Addr: net.IPv4(218, 51, 19, 10), - IPv4Net: &net.IPNet{ - IP: net.IPv4(218, 51, 19, 0), - Mask: net.CIDRMask(24, 32), - }, - IPv6Addr: net.ParseIP("fe80::42:daff:fe33:130a"), - IPv6Net: &net.IPNet{ - IP: net.ParseIP("fe80::"), - Mask: net.CIDRMask(64, 128), - }, - }, - "eth2": { - ID: 2615, - MAC: mustParseMAC("02:42:df:f5:e1:0a"), - IPv4Addr: net.IPv4(223, 245, 225, 10), - IPv4Net: &net.IPNet{ - IP: net.IPv4(223, 245, 225, 0), - Mask: net.CIDRMask(24, 32), - }, - IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"), - IPv6Net: &net.IPNet{ - IP: net.ParseIP("fe80::"), - Mask: net.CIDRMask(64, 128), - }, - }, - }, - }, - { - desc: "v4 only", - cmdOutput: ` -2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0 - valid_lft forever preferred_lft forever`, - want: map[string]DeviceInfo{ - "eth0": { - ID: 2613, - MAC: mustParseMAC("02:42:c0:a8:09:02"), - IPv4Addr: net.IPv4(192, 168, 9, 2), - IPv4Net: &net.IPNet{ - IP: net.IPv4(192, 168, 9, 0), - Mask: net.CIDRMask(24, 32), - }, - }, - }, - }, - { - desc: "v6 only", - cmdOutput: ` -2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative - valid_lft forever preferred_lft forever`, - want: map[string]DeviceInfo{ - "eth2": { - ID: 2615, - MAC: mustParseMAC("02:42:df:f5:e1:0a"), - IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"), - IPv6Net: &net.IPNet{ - IP: net.ParseIP("fe80::"), - Mask: net.CIDRMask(64, 128), - }, - }, - }, - }, - } { - t.Run(v.desc, func(t *testing.T) { - got, err := ParseDevices(v.cmdOutput) - if err != nil { - t.Errorf("ParseDevices(\n%s\n) got unexpected error: %s", v.cmdOutput, err) - } - if diff := cmp.Diff(v.want, got); diff != "" { - t.Errorf("ParseDevices(\n%s\n) got output diff (-want, +got):\n%s", v.cmdOutput, diff) - } - }) - } -} - -func TestParseDevicesErrors(t *testing.T) { - for _, v := range []struct { - desc string - cmdOutput string - }{ - { - desc: "invalid MAC addr", - cmdOutput: ` -2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:da:33:13:0a:ffffffff brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1 - valid_lft forever preferred_lft forever - inet6 fe80::42:daff:fe33:130a/64 scope link tentative - valid_lft forever preferred_lft forever`, - }, - { - desc: "invalid v4 addr", - cmdOutput: ` -2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 1234.4321.424242.0/24 brd 218.51.19.255 scope global eth1 - valid_lft forever preferred_lft forever - inet6 fe80::42:daff:fe33:130a/64 scope link tentative - valid_lft forever preferred_lft forever`, - }, - { - desc: "invalid v6 addr", - cmdOutput: ` -2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1 - valid_lft forever preferred_lft forever - inet6 fe80:ffffffff::42:daff:fe33:130a/64 scope link tentative - valid_lft forever preferred_lft forever`, - }, - { - desc: "invalid CIDR missing prefixlen", - cmdOutput: ` -2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default - link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 218.51.19.10 brd 218.51.19.255 scope global eth1 - valid_lft forever preferred_lft forever - inet6 fe80::42:daff:fe33:130a scope link tentative - valid_lft forever preferred_lft forever`, - }, - } { - t.Run(v.desc, func(t *testing.T) { - if _, err := ParseDevices(v.cmdOutput); err == nil { - t.Errorf("ParseDevices(\n%s\n) succeeded unexpectedly, want error", v.cmdOutput) - } - }) - } -} diff --git a/test/packetimpact/proto/BUILD b/test/packetimpact/proto/BUILD deleted file mode 100644 index 4a4370f42..000000000 --- a/test/packetimpact/proto/BUILD +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools:defs.bzl", "proto_library") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -proto_library( - name = "posix_server", - srcs = ["posix_server.proto"], - has_services = 1, -) diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto deleted file mode 100644 index 175a65336..000000000 --- a/test/packetimpact/proto/posix_server.proto +++ /dev/null @@ -1,268 +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. - -syntax = "proto3"; - -package posix_server; - -message SockaddrIn { - int32 family = 1; - uint32 port = 2; - bytes addr = 3; -} - -message SockaddrIn6 { - uint32 family = 1; - uint32 port = 2; - uint32 flowinfo = 3; - bytes addr = 4; - uint32 scope_id = 5; -} - -message Sockaddr { - oneof sockaddr { - SockaddrIn in = 1; - SockaddrIn6 in6 = 2; - } -} - -message Timeval { - int64 seconds = 1; - int64 microseconds = 2; -} - -message SockOptVal { - oneof val { - bytes bytesval = 1; - int32 intval = 2; - Timeval timeval = 3; - } -} - -// Request and Response pairs for each Posix service RPC call, sorted. - -message AcceptRequest { - int32 sockfd = 1; -} - -message AcceptResponse { - int32 fd = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - Sockaddr addr = 3; -} - -message BindRequest { - int32 sockfd = 1; - Sockaddr addr = 2; -} - -message BindResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message CloseRequest { - int32 fd = 1; -} - -message CloseResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message ConnectRequest { - int32 sockfd = 1; - Sockaddr addr = 2; -} - -message ConnectResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message GetSockNameRequest { - int32 sockfd = 1; -} - -message GetSockNameResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - Sockaddr addr = 3; -} - -message GetSockOptRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - int32 optlen = 4; - enum SockOptType { - UNSPECIFIED = 0; - BYTES = 1; - INT = 2; - TIME = 3; - } - SockOptType type = 5; -} - -message GetSockOptResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - SockOptVal optval = 3; -} - -message ListenRequest { - int32 sockfd = 1; - int32 backlog = 2; -} - -message ListenResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -// The events field is overloaded: when used for request, it is copied into the -// events field of posix struct pollfd; when used for response, it is filled by -// the revents field from the posix struct pollfd. -message PollFd { - int32 fd = 1; - uint32 events = 2; -} - -message PollRequest { - repeated PollFd pfds = 1; - int32 timeout_millis = 2; -} - -message PollResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - repeated PollFd pfds = 3; -} - -message SendRequest { - int32 sockfd = 1; - bytes buf = 2; - int32 flags = 3; -} - -message SendResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SendToRequest { - int32 sockfd = 1; - bytes buf = 2; - int32 flags = 3; - Sockaddr dest_addr = 4; -} - -message SendToResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SetNonblockingRequest { - int32 fd = 1; - bool nonblocking = 2; -} - -message SetNonblockingResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - // The failed fcntl cmd. - string cmd = 3; -} - -message SetSockOptRequest { - int32 sockfd = 1; - int32 level = 2; - int32 optname = 3; - SockOptVal optval = 4; -} - -message SetSockOptResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message SocketRequest { - int32 domain = 1; - int32 type = 2; - int32 protocol = 3; -} - -message SocketResponse { - int32 fd = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message ShutdownRequest { - int32 fd = 1; - int32 how = 2; -} - -message ShutdownResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. -} - -message RecvRequest { - int32 sockfd = 1; - int32 len = 2; - int32 flags = 3; -} - -message RecvResponse { - int32 ret = 1; - int32 errno_ = 2; // "errno" may fail to compile in c++. - bytes buf = 3; -} - -service Posix { - // Call accept() on the DUT. - rpc Accept(AcceptRequest) returns (AcceptResponse); - // Call bind() on the DUT. - rpc Bind(BindRequest) returns (BindResponse); - // Call close() on the DUT. - rpc Close(CloseRequest) returns (CloseResponse); - // Call connect() on the DUT. - rpc Connect(ConnectRequest) returns (ConnectResponse); - // Call getsockname() on the DUT. - rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse); - // Call getsockopt() on the DUT. - rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse); - // Call listen() on the DUT. - rpc Listen(ListenRequest) returns (ListenResponse); - // Call poll() on the DUT. Only pollfds that have non-empty revents are - // returned, the only way to tie the response back to the original request - // is using the fd number. - rpc Poll(PollRequest) returns (PollResponse); - // Call send() on the DUT. - rpc Send(SendRequest) returns (SendResponse); - // Call sendto() on the DUT. - rpc SendTo(SendToRequest) returns (SendToResponse); - // Set/Clear O_NONBLOCK flag on the requested fd. This is needed because the - // operating system on DUT may have a different definition for O_NONBLOCK, it - // is not sound to assemble flags on testbench. - rpc SetNonblocking(SetNonblockingRequest) returns (SetNonblockingResponse); - // Call setsockopt() on the DUT. - rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse); - // Call socket() on the DUT. - rpc Socket(SocketRequest) returns (SocketResponse); - // Call shutdown() on the DUT. - rpc Shutdown(ShutdownRequest) returns (ShutdownResponse); - // Call recv() on the DUT. - rpc Recv(RecvRequest) returns (RecvResponse); -} diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD deleted file mode 100644 index 888c44343..000000000 --- a/test/packetimpact/runner/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defs.bzl", "bzl_library", "go_library", "go_test") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -go_test( - name = "packetimpact_test", - srcs = [ - "packetimpact_test.go", - ], - tags = [ - # Not intended to be run directly. - "local", - "manual", - ], - deps = [":runner"], -) - -bzl_library( - name = "defs_bzl", - srcs = ["defs.bzl"], - visibility = ["//test/packetimpact:__subpackages__"], -) - -go_library( - name = "runner", - testonly = True, - srcs = ["dut.go"], - visibility = ["//test/packetimpact:__subpackages__"], - deps = [ - "//pkg/test/dockerutil", - "//test/packetimpact/netdevs", - "//test/packetimpact/testbench", - "@com_github_docker_docker//api/types/mount:go_default_library", - ], -) diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl deleted file mode 100644 index 803a87a07..000000000 --- a/test/packetimpact/runner/defs.bzl +++ /dev/null @@ -1,311 +0,0 @@ -"""Defines rules for packetimpact test targets.""" - -load("//tools:defs.bzl", "go_test") - -def _packetimpact_test_impl(ctx): - test_runner = ctx.executable._test_runner - bench = ctx.actions.declare_file("%s-bench" % ctx.label.name) - bench_content = "\n".join([ - "#!/bin/bash", - # This test will run part in a distinct user namespace. This can cause - # permission problems, because all runfiles may not be owned by the - # current user, and no other users will be mapped in that namespace. - # Make sure that everything is readable here. - "find . -type f -or -type d -exec chmod a+rx {} \\;", - "%s %s --testbench_binary %s --num_duts %d $@\n" % ( - test_runner.short_path, - " ".join(ctx.attr.flags), - ctx.files.testbench_binary[0].short_path, - ctx.attr.num_duts, - ), - ]) - ctx.actions.write(bench, bench_content, is_executable = True) - - transitive_files = [] - if hasattr(ctx.attr._test_runner, "data_runfiles"): - transitive_files.append(ctx.attr._test_runner.data_runfiles.files) - files = [test_runner] + ctx.files.testbench_binary + ctx.files._posix_server - runfiles = ctx.runfiles( - files = files, - transitive_files = depset(transitive = transitive_files), - collect_default = True, - collect_data = True, - ) - return [DefaultInfo(executable = bench, runfiles = runfiles)] - -_packetimpact_test = rule( - attrs = { - "_test_runner": attr.label( - executable = True, - cfg = "target", - default = ":packetimpact_test", - ), - "_posix_server": attr.label( - cfg = "target", - default = "//test/packetimpact/dut:posix_server", - ), - "testbench_binary": attr.label( - cfg = "target", - mandatory = True, - ), - "flags": attr.string_list( - mandatory = False, - default = [], - ), - "num_duts": attr.int( - mandatory = False, - default = 1, - ), - }, - test = True, - implementation = _packetimpact_test_impl, -) - -PACKETIMPACT_TAGS = [ - "local", - "manual", - "packetimpact", -] - -def packetimpact_native_test( - name, - testbench_binary, - expect_failure = False, - **kwargs): - """Add a native packetimpact test. - - Args: - name: name of the test - testbench_binary: the testbench binary - expect_failure: the test must fail - **kwargs: all the other args, forwarded to _packetimpact_test - """ - expect_failure_flag = ["--expect_failure"] if expect_failure else [] - _packetimpact_test( - name = name + "_native_test", - testbench_binary = testbench_binary, - flags = ["--native"] + expect_failure_flag, - tags = PACKETIMPACT_TAGS, - **kwargs - ) - -def packetimpact_netstack_test( - name, - testbench_binary, - expect_failure = False, - **kwargs): - """Add a packetimpact test on netstack. - - Args: - name: name of the test - testbench_binary: the testbench binary - expect_failure: the test must fail - **kwargs: all the other args, forwarded to _packetimpact_test - """ - expect_failure_flag = [] - if expect_failure: - expect_failure_flag = ["--expect_failure"] - _packetimpact_test( - name = name + "_netstack_test", - testbench_binary = testbench_binary, - # Note that a distinct runtime must be provided in the form - # --test_arg=--runtime=other when invoking bazel. - flags = expect_failure_flag, - tags = PACKETIMPACT_TAGS, - **kwargs - ) - -def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1): - """Add packetimpact tests written in go. - - Args: - name: name of the test - expect_native_failure: the test must fail natively - expect_netstack_failure: the test must fail for Netstack - num_duts: how many DUTs are needed for the test - """ - testbench_binary = name + "_test" - packetimpact_native_test( - name = name, - expect_failure = expect_native_failure, - num_duts = num_duts, - testbench_binary = testbench_binary, - ) - packetimpact_netstack_test( - name = name, - expect_failure = expect_netstack_failure, - num_duts = num_duts, - testbench_binary = testbench_binary, - ) - -def packetimpact_testbench(name, size = "small", pure = True, **kwargs): - """Build packetimpact testbench written in go. - - Args: - name: name of the test - size: size of the test - pure: make a static go binary - **kwargs: all the other args, forwarded to go_test - """ - go_test( - name = name + "_test", - size = size, - pure = pure, - nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems. - tags = [ - "local", - "manual", - ], - **kwargs - ) - -PacketimpactTestInfo = provider( - doc = "Provide information for packetimpact tests", - fields = [ - "name", - "expect_netstack_failure", - "num_duts", - ], -) - -ALL_TESTS = [ - PacketimpactTestInfo( - name = "fin_wait2_timeout", - ), - PacketimpactTestInfo( - name = "ipv4_id_uniqueness", - ), - PacketimpactTestInfo( - name = "udp_discard_mcast_source_addr", - ), - PacketimpactTestInfo( - name = "udp_any_addr_recv_unicast", - ), - PacketimpactTestInfo( - name = "udp_icmp_error_propagation", - ), - PacketimpactTestInfo( - name = "tcp_window_shrink", - ), - PacketimpactTestInfo( - name = "tcp_zero_window_probe", - ), - PacketimpactTestInfo( - name = "tcp_zero_window_probe_retransmit", - ), - PacketimpactTestInfo( - name = "tcp_zero_window_probe_usertimeout", - ), - PacketimpactTestInfo( - name = "tcp_retransmits", - ), - PacketimpactTestInfo( - name = "tcp_outside_the_window", - ), - PacketimpactTestInfo( - name = "tcp_outside_the_window_closing", - # TODO(b/181625316): Fix netstack then merge into tcp_outside_the_window. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( - name = "tcp_noaccept_close_rst", - ), - PacketimpactTestInfo( - name = "tcp_send_window_sizes_piggyback", - ), - PacketimpactTestInfo( - name = "tcp_unacc_seq_ack", - ), - PacketimpactTestInfo( - name = "tcp_unacc_seq_ack_closing", - # TODO(b/181625316): Fix netstack then merge into tcp_unacc_seq_ack. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( - name = "tcp_paws_mechanism", - # TODO(b/156682000): Fix netstack then remove the line below. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( - name = "tcp_user_timeout", - ), - PacketimpactTestInfo( - name = "tcp_zero_receive_window", - ), - PacketimpactTestInfo( - name = "tcp_queue_send_recv_in_syn_sent", - ), - PacketimpactTestInfo( - name = "tcp_synsent_reset", - ), - PacketimpactTestInfo( - name = "tcp_synrcvd_reset", - ), - PacketimpactTestInfo( - name = "tcp_network_unreachable", - ), - PacketimpactTestInfo( - name = "tcp_cork_mss", - ), - PacketimpactTestInfo( - name = "tcp_handshake_window_size", - ), - PacketimpactTestInfo( - name = "tcp_timewait_reset", - # TODO(b/168523247): Fix netstack then remove the line below. - expect_netstack_failure = True, - ), - PacketimpactTestInfo( - name = "icmpv6_param_problem", - ), - PacketimpactTestInfo( - name = "ipv6_unknown_options_action", - ), - PacketimpactTestInfo( - name = "ipv4_fragment_reassembly", - ), - PacketimpactTestInfo( - name = "ipv6_fragment_reassembly", - ), - PacketimpactTestInfo( - name = "ipv6_fragment_icmp_error", - num_duts = 3, - ), - PacketimpactTestInfo( - name = "udp_send_recv_dgram", - ), - PacketimpactTestInfo( - name = "tcp_linger", - ), - PacketimpactTestInfo( - name = "tcp_rcv_buf_space", - ), - PacketimpactTestInfo( - name = "tcp_rack", - expect_netstack_failure = True, - ), - PacketimpactTestInfo( - name = "tcp_info", - ), - PacketimpactTestInfo( - name = "tcp_fin_retransmission", - ), -] - -def validate_all_tests(): - """ - Make sure that ALL_TESTS list is in sync with the rules in BUILD. - - This function is order-dependent, it is intended to be used after - all packetimpact_testbench rules and before using ALL_TESTS list - at the end of BUILD. - """ - all_tests_dict = {} # there is no set, using dict to approximate. - for test in ALL_TESTS: - rule_name = test.name + "_test" - all_tests_dict[rule_name] = True - if not native.existing_rule(rule_name): - fail("%s does not have a packetimpact_testbench rule in BUILD" % test.name) - for name in native.existing_rules(): - if name.endswith("_test") and name not in all_tests_dict: - fail("%s is not declared in ALL_TESTS list in defs.bzl" % name[:-5]) diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go deleted file mode 100644 index 1064ca976..000000000 --- a/test/packetimpact/runner/dut.go +++ /dev/null @@ -1,646 +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 runner starts docker containers and networking for a packetimpact test. -package runner - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log" - "math/rand" - "net" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/docker/docker/api/types/mount" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/packetimpact/netdevs" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -// stringList implements flag.Value. -type stringList []string - -// String implements flag.Value.String. -func (l *stringList) String() string { - return strings.Join(*l, ",") -} - -// Set implements flag.Value.Set. -func (l *stringList) Set(value string) error { - *l = append(*l, value) - return nil -} - -var ( - native = false - testbenchBinary = "" - tshark = false - extraTestArgs = stringList{} - expectFailure = false - numDUTs = 1 - - // DUTAddr is the IP addres for DUT. - DUTAddr = net.IPv4(0, 0, 0, 10) - testbenchAddr = net.IPv4(0, 0, 0, 20) -) - -// RegisterFlags defines flags and associates them with the package-level -// exported variables above. It should be called by tests in their init -// functions. -func RegisterFlags(fs *flag.FlagSet) { - fs.BoolVar(&native, "native", false, "whether the test should be run natively") - fs.StringVar(&testbenchBinary, "testbench_binary", "", "path to the testbench binary") - fs.BoolVar(&tshark, "tshark", false, "use more verbose tshark in logs instead of tcpdump") - fs.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench") - fs.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run") - fs.IntVar(&numDUTs, "num_duts", numDUTs, "the number of duts to create") -} - -const ( - // CtrlPort is the port that posix_server listens on. - CtrlPort uint16 = 40000 - // testOutputDir is the directory in each container that holds test output. - testOutputDir = "/tmp/testoutput" -) - -// logger implements testutil.Logger. -// -// Labels logs based on their source and formats multi-line logs. -type logger string - -// Name implements testutil.Logger.Name. -func (l logger) Name() string { - return string(l) -} - -// Logf implements testutil.Logger.Logf. -func (l logger) Logf(format string, args ...interface{}) { - lines := strings.Split(fmt.Sprintf(format, args...), "\n") - log.Printf("%s: %s", l, lines[0]) - for _, line := range lines[1:] { - log.Printf("%*s %s", len(l), "", line) - } -} - -// dutInfo encapsulates all the essential information to set up testbench -// container. -type dutInfo struct { - dut DUT - ctrlNet, testNet *dockerutil.Network - netInfo *testbench.DUTTestNet - uname *testbench.DUTUname -} - -// setUpDUT will set up one DUT and return information for setting up the -// container for testbench. -func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerutil.Container) DUT) (dutInfo, error) { - // Create the networks needed for the test. One control network is needed - // for the gRPC control packets and one test network on which to transmit - // the test packets. - var info dutInfo - ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet")) - testNet := dockerutil.NewNetwork(ctx, logger("testNet")) - for _, dn := range []*dockerutil.Network{ctrlNet, testNet} { - for { - if err := createDockerNetwork(ctx, dn); err != nil { - t.Log("creating docker network:", err) - const wait = 100 * time.Millisecond - t.Logf("sleeping %s and will try creating docker network again", wait) - // This can fail if another docker network claimed the same IP so we - // will just try again. - time.Sleep(wait) - continue - } - break - } - dn := dn - t.Cleanup(func() { - if err := dn.Cleanup(ctx); err != nil { - t.Errorf("unable to cleanup container %s: %s", dn.Name, err) - } - }) - // Sanity check. - if inspect, err := dn.Inspect(ctx); err != nil { - return dutInfo{}, fmt.Errorf("failed to inspect network %s: %w", dn.Name, err) - } else if inspect.Name != dn.Name { - return dutInfo{}, fmt.Errorf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name) - } - } - info.ctrlNet = ctrlNet - info.testNet = testNet - - // Create the Docker container for the DUT. - var dut DUT - if native { - dut = mkDevice(dockerutil.MakeNativeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) - } else { - dut = mkDevice(dockerutil.MakeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) - } - info.dut = dut - - runOpts := dockerutil.RunOpts{ - Image: "packetimpact", - CapAdd: []string{"NET_ADMIN"}, - } - if _, err := MountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil { - return dutInfo{}, err - } - - ipv4PrefixLength, _ := testNet.Subnet.Mask.Size() - remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := dut.Prepare(ctx, t, runOpts, ctrlNet, testNet) - if err != nil { - return dutInfo{}, err - } - info.netInfo = &testbench.DUTTestNet{ - RemoteMAC: remoteMAC, - RemoteIPv4: AddressInSubnet(DUTAddr, *testNet.Subnet), - RemoteIPv6: remoteIPv6, - RemoteDevID: dutDeviceID, - RemoteDevName: dutTestNetDev, - LocalIPv4: AddressInSubnet(testbenchAddr, *testNet.Subnet), - IPv4PrefixLength: ipv4PrefixLength, - POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet), - POSIXServerPort: CtrlPort, - } - info.uname, err = dut.Uname(ctx) - if err != nil { - return dutInfo{}, fmt.Errorf("failed to get uname information on DUT: %w", err) - } - return info, nil -} - -// TestWithDUT runs a packetimpact test with the given information. -func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT) { - if testbenchBinary == "" { - t.Fatal("--testbench_binary is missing") - } - dockerutil.EnsureSupportedDockerVersion() - - dutInfoChan := make(chan dutInfo, numDUTs) - errChan := make(chan error, numDUTs) - var dockerNetworks []*dockerutil.Network - var dutInfos []*testbench.DUTInfo - var duts []DUT - - setUpCtx, cancelSetup := context.WithCancel(ctx) - t.Cleanup(cancelSetup) - for i := 0; i < numDUTs; i++ { - go func(i int) { - info, err := setUpDUT(setUpCtx, t, i, mkDevice) - if err != nil { - errChan <- err - } else { - dutInfoChan <- info - } - }(i) - } - for i := 0; i < numDUTs; i++ { - select { - case info := <-dutInfoChan: - dockerNetworks = append(dockerNetworks, info.ctrlNet, info.testNet) - dutInfos = append(dutInfos, &testbench.DUTInfo{ - Net: info.netInfo, - Uname: info.uname, - }) - duts = append(duts, info.dut) - case err := <-errChan: - t.Fatal(err) - } - } - - // Create the Docker container for the testbench. - testbenchContainer := dockerutil.MakeNativeContainer(ctx, logger("testbench")) - - runOpts := dockerutil.RunOpts{ - Image: "packetimpact", - CapAdd: []string{"NET_ADMIN"}, - } - if _, err := MountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil { - t.Fatal(err) - } - tbb := path.Base(testbenchBinary) - containerTestbenchBinary := filepath.Join("/packetimpact", tbb) - testbenchContainer.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb)) - - if err := StartContainer( - ctx, - runOpts, - testbenchContainer, - testbenchAddr, - dockerNetworks, - nil, /* sysctls */ - "tail", "-f", "/dev/null", - ); err != nil { - t.Fatalf("cannot start testbench container: %s", err) - } - - for i := range dutInfos { - name, info, err := deviceByIP(ctx, testbenchContainer, dutInfos[i].Net.LocalIPv4) - if err != nil { - t.Fatalf("failed to get the device name associated with %s: %s", dutInfos[i].Net.LocalIPv4, err) - } - dutInfos[i].Net.LocalDevName = name - dutInfos[i].Net.LocalDevID = info.ID - dutInfos[i].Net.LocalMAC = info.MAC - localIPv6, err := getOrAssignIPv6Addr(ctx, testbenchContainer, name) - if err != nil { - t.Fatalf("failed to get IPV6 address on %s: %s", testbenchContainer.Name, err) - } - dutInfos[i].Net.LocalIPv6 = localIPv6 - } - dutInfosBytes, err := json.Marshal(dutInfos) - if err != nil { - t.Fatalf("failed to marshal %v into json: %s", dutInfos, err) - } - - baseSnifferArgs := []string{ - "tcpdump", - "-vvv", - "--absolute-tcp-sequence-numbers", - "--packet-buffered", - // Disable DNS resolution. - "-n", - // run tcpdump as root since the output directory is owned by root. From - // `man tcpdump`: - // - // -Z user - // --relinquish-privileges=user - // If tcpdump is running as root, after opening the capture device - // or input savefile, change the user ID to user and the group ID to - // the primary group of user. - // This behavior is enabled by default (-Z tcpdump), and can be - // disabled by -Z root. - "-Z", "root", - } - if tshark { - baseSnifferArgs = []string{ - "tshark", - "-V", - "-o", "tcp.check_checksum:TRUE", - "-o", "udp.check_checksum:TRUE", - // Disable buffering. - "-l", - // Disable DNS resolution. - "-n", - } - } - for _, info := range dutInfos { - n := info.Net - snifferArgs := append(baseSnifferArgs, "-i", n.LocalDevName) - if !tshark { - snifferArgs = append( - snifferArgs, - "-w", - filepath.Join(testOutputDir, fmt.Sprintf("%s.pcap", n.LocalDevName)), - ) - } - p, err := testbenchContainer.ExecProcess(ctx, dockerutil.ExecOpts{}, snifferArgs...) - if err != nil { - t.Fatalf("failed to start exec a sniffer on %s: %s", n.LocalDevName, err) - } - t.Cleanup(func() { - if snifferOut, err := p.Logs(); err != nil { - t.Errorf("sniffer logs failed: %s\n%s", err, snifferOut) - } else { - t.Logf("sniffer logs:\n%s", snifferOut) - } - }) - // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it - // will respond with an RST. In most packetimpact tests, the SYN is sent - // by the raw socket, the kernel knows nothing about the connection, this - // behavior will break lots of TCP related packetimpact tests. To prevent - // this, we can install the following iptables rules. The raw socket that - // packetimpact tests use will still be able to see everything. - for _, bin := range []string{"iptables", "ip6tables"} { - if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", n.LocalDevName, "-p", "tcp", "-j", "DROP"); err != nil { - t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbenchContainer.Name, err, logs) - } - } - } - - t.Cleanup(func() { - // Wait 1 second before killing tcpdump to give it time to flush - // any packets. On linux tests killing it immediately can - // sometimes result in partial pcaps. - time.Sleep(1 * time.Second) - if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil { - t.Errorf("failed to kill all sniffers: %s, logs: %s", err, logs) - } - }) - - // FIXME(b/156449515): Some piece of the system has a race. The old - // bash script version had a sleep, so we have one too. The race should - // be fixed and this sleep removed. - time.Sleep(time.Second) - - // Start a packetimpact test on the test bench. The packetimpact test sends - // and receives packets and also sends POSIX socket commands to the - // posix_server to be executed on the DUT. - testArgs := []string{containerTestbenchBinary} - testArgs = append(testArgs, extraTestArgs...) - testArgs = append(testArgs, - fmt.Sprintf("--native=%t", native), - "--dut_infos_json", string(dutInfosBytes), - ) - testbenchLogs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, testArgs...) - if (err != nil) != expectFailure { - var dutLogs string - for i, dut := range duts { - logs, err := dut.Logs(ctx) - if err != nil { - logs = fmt.Sprintf("failed to fetch DUT logs: %s", err) - } - dutLogs = fmt.Sprintf(`%s====== Begin of DUT-%d Logs ====== - -%s - -====== End of DUT-%d Logs ====== - -`, dutLogs, i, logs, i) - } - - t.Errorf(`test error: %v, expect failure: %t - -%s====== Begin of Testbench Logs ====== - -%s - -====== End of Testbench Logs ======`, - err, expectFailure, dutLogs, testbenchLogs) - } -} - -// DUT describes how to setup/teardown the dut for packetimpact tests. -type DUT interface { - // Prepare prepares the dut, starts posix_server and returns the IPv6, MAC - // address, the interface ID, and the interface name for the testNet on DUT. - // The t parameter is supposed to be used for t.Cleanup. Don't use it for - // t.Fatal/FailNow functions. - Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) - - // Uname gathers information of DUT using command uname. - Uname(ctx context.Context) (*testbench.DUTUname, error) - - // Logs retrieves the logs from the dut. - Logs(ctx context.Context) (string, error) -} - -// DockerDUT describes a docker based DUT. -type DockerDUT struct { - c *dockerutil.Container -} - -// NewDockerDUT creates a docker based DUT. -func NewDockerDUT(c *dockerutil.Container) DUT { - return &DockerDUT{ - c: c, - } -} - -// Prepare implements DUT.Prepare. -func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) { - const containerPosixServerBinary = "/packetimpact/posix_server" - dut.c.CopyFiles(&runOpts, "/packetimpact", "test/packetimpact/dut/posix_server") - - if err := StartContainer( - ctx, - runOpts, - dut.c, - DUTAddr, - []*dockerutil.Network{ctrlNet, testNet}, - map[string]string{ - // This enables creating ICMP sockets on Linux. - "net.ipv4.ping_group_range": "0 0", - }, - containerPosixServerBinary, - "--ip=0.0.0.0", - fmt.Sprintf("--port=%d", CtrlPort), - ); err != nil { - return nil, nil, 0, "", fmt.Errorf("failed to start docker container for DUT: %w", err) - } - - if _, err := dut.c.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil { - return nil, nil, 0, "", fmt.Errorf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err) - } - - dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DUTAddr, *testNet.Subnet)) - if err != nil { - return nil, nil, 0, "", err - } - - remoteIPv6, err := getOrAssignIPv6Addr(ctx, dut.c, dutTestDevice) - if err != nil { - return nil, nil, 0, "", fmt.Errorf("failed to get IPv6 address on %s: %s", dut.c.Name, err) - } - const testNetDev = "eth2" - - return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev, nil -} - -// Uname implements DUT.Uname. -func (dut *DockerDUT) Uname(ctx context.Context) (*testbench.DUTUname, error) { - machine, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-m") - if err != nil { - return nil, err - } - kernelRelease, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-r") - if err != nil { - return nil, err - } - kernelVersion, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-v") - if err != nil { - return nil, err - } - kernelName, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-s") - if err != nil { - return nil, err - } - // TODO(gvisor.dev/issues/5586): -o is not supported on macOS. - operatingSystem, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-o") - if err != nil { - return nil, err - } - return &testbench.DUTUname{ - Machine: strings.TrimRight(machine, "\n"), - KernelName: strings.TrimRight(kernelName, "\n"), - KernelRelease: strings.TrimRight(kernelRelease, "\n"), - KernelVersion: strings.TrimRight(kernelVersion, "\n"), - OperatingSystem: strings.TrimRight(operatingSystem, "\n"), - }, nil -} - -// Logs implements DUT.Logs. -func (dut *DockerDUT) Logs(ctx context.Context) (string, error) { - logs, err := dut.c.Logs(ctx) - if err != nil { - return "", err - } - return logs, nil -} - -// AddNetworks connects docker network with the container and assigns the specific IP. -func AddNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, networks []*dockerutil.Network) error { - for _, dn := range networks { - ip := AddressInSubnet(addr, *dn.Subnet) - // Connect to the network with the specified IP address. - if err := dn.Connect(ctx, d, ip.String(), ""); err != nil { - return fmt.Errorf("unable to connect container %s to network %s: %w", d.Name, dn.Name, err) - } - } - return nil -} - -// AddressInSubnet combines the subnet provided with the address and returns a -// new address. The return address bits come from the subnet where the mask is -// 1 and from the ip address where the mask is 0. -func AddressInSubnet(addr net.IP, subnet net.IPNet) net.IP { - var octets net.IP - for i := 0; i < 4; i++ { - octets = append(octets, (subnet.IP.To4()[i]&subnet.Mask[i])+(addr.To4()[i]&(^subnet.Mask[i]))) - } - return octets -} - -// devicesInfo will run "ip addr show" on the container and parse the output -// to a map[string]netdevs.DeviceInfo. -func devicesInfo(ctx context.Context, d *dockerutil.Container) (map[string]netdevs.DeviceInfo, error) { - out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "show") - if err != nil { - return map[string]netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w\n%s", d.Name, err, out) - } - devs, err := netdevs.ParseDevices(out) - if err != nil { - return map[string]netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out) - } - return devs, nil -} - -// deviceByIP finds a deviceInfo and device name from an IP address. -func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) { - devs, err := devicesInfo(ctx, d) - if err != nil { - return "", netdevs.DeviceInfo{}, err - } - testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs) - if err != nil { - return "", netdevs.DeviceInfo{}, fmt.Errorf("can't find deviceInfo for container %s: %w", d.Name, err) - } - return testDevice, deviceInfo, nil -} - -// getOrAssignIPv6Addr will try to get the IPv6 address for the interface; if an -// address was not assigned, a link-local address based on MAC will be assigned -// to that interface. -func getOrAssignIPv6Addr(ctx context.Context, d *dockerutil.Container, iface string) (net.IP, error) { - devs, err := devicesInfo(ctx, d) - if err != nil { - return net.IP{}, err - } - info := devs[iface] - if info.IPv6Addr != nil { - return info.IPv6Addr, nil - } - if info.MAC == nil { - return nil, fmt.Errorf("unable to find MAC address of %s", iface) - } - if logs, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(info.MAC).String(), "scope", "link", "dev", iface); err != nil { - return net.IP{}, fmt.Errorf("unable to ip addr add on container %s: %w, logs: %s", d.Name, err, logs) - } - // Now try again, to make sure that it worked. - devs, err = devicesInfo(ctx, d) - if err != nil { - return net.IP{}, err - } - info = devs[iface] - if info.IPv6Addr == nil { - return net.IP{}, fmt.Errorf("unable to set IPv6 address on container %s", d.Name) - } - return info.IPv6Addr, nil -} - -// createDockerNetwork makes a randomly-named network that will start with the -// namePrefix. The network will be a random /24 subnet. -func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error { - randSource := rand.NewSource(time.Now().UnixNano()) - r1 := rand.New(randSource) - // Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24. - ip := net.IPv4(byte(r1.Intn(224-192)+192), byte(r1.Intn(256)), byte(r1.Intn(256)), 0) - n.Subnet = &net.IPNet{ - IP: ip, - Mask: ip.DefaultMask(), - } - return n.Create(ctx) -} - -// StartContainer will create a container instance from runOpts, connect it -// with the specified docker networks and start executing the specified cmd. -func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerutil.Container, containerAddr net.IP, ns []*dockerutil.Network, sysctls map[string]string, cmd ...string) error { - conf, hostconf, netconf := c.ConfigsFrom(runOpts, cmd...) - _ = netconf - hostconf.AutoRemove = true - hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"} - for k, v := range sysctls { - hostconf.Sysctls[k] = v - } - - if err := c.CreateFrom(ctx, runOpts.Image, conf, hostconf, nil); err != nil { - return fmt.Errorf("unable to create container %s: %w", c.Name, err) - } - - if err := AddNetworks(ctx, c, containerAddr, ns); err != nil { - return fmt.Errorf("unable to connect the container with the networks: %w", err) - } - - if err := c.Start(ctx); err != nil { - return fmt.Errorf("unable to start container %s: %w", c.Name, err) - } - return nil -} - -// MountTempDirectory creates a temporary directory on host with the template -// and then mounts it into the container under the name provided. The temporary -// directory name is returned. Content in that directory will be copied to -// TEST_UNDECLARED_OUTPUTS_DIR in cleanup phase. -func MountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) { - t.Helper() - tmpDir, err := ioutil.TempDir("", hostDirTemplate) - if err != nil { - return "", fmt.Errorf("failed to create a temp dir: %w", err) - } - t.Cleanup(func() { - if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil { - t.Errorf("unable to copy container output files: %s", err) - } - if err := os.RemoveAll(tmpDir); err != nil { - t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err) - } - }) - runOpts.Mounts = append(runOpts.Mounts, mount.Mount{ - Type: mount.TypeBind, - Source: tmpDir, - Target: containerDir, - ReadOnly: false, - }) - return tmpDir, nil -} diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go deleted file mode 100644 index 46334b7ab..000000000 --- a/test/packetimpact/runner/packetimpact_test.go +++ /dev/null @@ -1,32 +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. - -// The runner starts docker containers and networking for a packetimpact test. -package packetimpact_test - -import ( - "context" - "flag" - "testing" - - "gvisor.dev/gvisor/test/packetimpact/runner" -) - -func init() { - runner.RegisterFlags(flag.CommandLine) -} - -func TestOne(t *testing.T) { - runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT) -} diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD deleted file mode 100644 index 43b4c7ca1..000000000 --- a/test/packetimpact/testbench/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package( - licenses = ["notice"], -) - -go_library( - name = "testbench", - srcs = [ - "connections.go", - "dut.go", - "dut_client.go", - "layers.go", - "rawsockets.go", - "testbench.go", - ], - visibility = ["//test/packetimpact:__subpackages__"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//pkg/usermem", - "//test/packetimpact/proto:posix_server_go_proto", - "@com_github_google_go_cmp//cmp:go_default_library", - "@com_github_google_go_cmp//cmp/cmpopts:go_default_library", - "@com_github_mohae_deepcopy//:go_default_library", - "@org_golang_google_grpc//:go_default_library", - "@org_golang_google_grpc//keepalive:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - "@org_uber_go_multierr//:go_default_library", - ], -) - -go_test( - name = "testbench_test", - size = "small", - srcs = ["layers_test.go"], - library = ":testbench", - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "@com_github_mohae_deepcopy//:go_default_library", - ], -) diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go deleted file mode 100644 index 8ad9040ff..000000000 --- a/test/packetimpact/testbench/connections.go +++ /dev/null @@ -1,1243 +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 testbench - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/mohae/deepcopy" - "go.uber.org/multierr" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" -) - -func portFromSockaddr(sa unix.Sockaddr) (uint16, error) { - switch sa := sa.(type) { - case *unix.SockaddrInet4: - return uint16(sa.Port), nil - case *unix.SockaddrInet6: - return uint16(sa.Port), nil - } - return 0, fmt.Errorf("sockaddr type %T does not contain port", sa) -} - -// pickPort makes a new socket and returns the socket FD and port. The domain -// should be AF_INET or AF_INET6. The caller must close the FD when done with -// the port if there is no error. -func (n *DUTTestNet) pickPort(domain, typ int) (fd int, port uint16, err error) { - fd, err = unix.Socket(domain, typ, 0) - if err != nil { - return -1, 0, fmt.Errorf("creating socket: %w", err) - } - defer func() { - if err != nil { - if cerr := unix.Close(fd); cerr != nil { - err = multierr.Append(err, fmt.Errorf("failed to close socket %d: %w", fd, cerr)) - } - } - }() - var sa unix.Sockaddr - switch domain { - case unix.AF_INET: - var sa4 unix.SockaddrInet4 - copy(sa4.Addr[:], n.LocalIPv4) - sa = &sa4 - case unix.AF_INET6: - sa6 := unix.SockaddrInet6{ZoneId: n.LocalDevID} - copy(sa6.Addr[:], n.LocalIPv6) - sa = &sa6 - default: - return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain) - } - if err = unix.Bind(fd, sa); err != nil { - return -1, 0, fmt.Errorf("binding to %+v: %w", sa, err) - } - sa, err = unix.Getsockname(fd) - if err != nil { - return -1, 0, fmt.Errorf("unix.Getsocketname(%d): %w", fd, err) - } - port, err = portFromSockaddr(sa) - if err != nil { - return -1, 0, fmt.Errorf("extracting port from socket address %+v: %w", sa, err) - } - return fd, port, nil -} - -// layerState stores the state of a layer of a connection. -type layerState interface { - // outgoing returns an outgoing layer to be sent in a frame. It should not - // update layerState, that is done in layerState.sent. - outgoing() Layer - - // incoming creates an expected Layer for comparing against a received Layer. - // Because the expectation can depend on values in the received Layer, it is - // an input to incoming. For example, the ACK number needs to be checked in a - // TCP packet but only if the ACK flag is set in the received packet. It - // should not update layerState, that is done in layerState.received. The - // caller takes ownership of the returned Layer. - incoming(received Layer) Layer - - // sent updates the layerState based on the Layer that was sent. The input is - // a Layer with all prev and next pointers populated so that the entire frame - // as it was sent is available. - sent(sent Layer) error - - // received updates the layerState based on a Layer that is received. The - // input is a Layer with all prev and next pointers populated so that the - // entire frame as it was received is available. - received(received Layer) error - - // close frees associated resources held by the LayerState. - close() error -} - -// etherState maintains state about an Ethernet connection. -type etherState struct { - out, in Ether -} - -var _ layerState = (*etherState)(nil) - -// newEtherState creates a new etherState. -func (n *DUTTestNet) newEtherState(out, in Ether) (*etherState, error) { - lmac := tcpip.LinkAddress(n.LocalMAC) - rmac := tcpip.LinkAddress(n.RemoteMAC) - s := etherState{ - out: Ether{SrcAddr: &lmac, DstAddr: &rmac}, - in: Ether{SrcAddr: &rmac, DstAddr: &lmac}, - } - if err := s.out.merge(&out); err != nil { - return nil, err - } - if err := s.in.merge(&in); err != nil { - return nil, err - } - return &s, nil -} - -func (s *etherState) outgoing() Layer { - return deepcopy.Copy(&s.out).(Layer) -} - -// incoming implements layerState.incoming. -func (s *etherState) incoming(Layer) Layer { - return deepcopy.Copy(&s.in).(Layer) -} - -func (*etherState) sent(Layer) error { - return nil -} - -func (*etherState) received(Layer) error { - return nil -} - -func (*etherState) close() error { - return nil -} - -// ipv4State maintains state about an IPv4 connection. -type ipv4State struct { - out, in IPv4 -} - -var _ layerState = (*ipv4State)(nil) - -// newIPv4State creates a new ipv4State. -func (n *DUTTestNet) newIPv4State(out, in IPv4) (*ipv4State, error) { - lIP := tcpip.Address(n.LocalIPv4) - rIP := tcpip.Address(n.RemoteIPv4) - s := ipv4State{ - out: IPv4{SrcAddr: &lIP, DstAddr: &rIP}, - in: IPv4{SrcAddr: &rIP, DstAddr: &lIP}, - } - if err := s.out.merge(&out); err != nil { - return nil, err - } - if err := s.in.merge(&in); err != nil { - return nil, err - } - return &s, nil -} - -func (s *ipv4State) outgoing() Layer { - return deepcopy.Copy(&s.out).(Layer) -} - -// incoming implements layerState.incoming. -func (s *ipv4State) incoming(Layer) Layer { - return deepcopy.Copy(&s.in).(Layer) -} - -func (*ipv4State) sent(Layer) error { - return nil -} - -func (*ipv4State) received(Layer) error { - return nil -} - -func (*ipv4State) close() error { - return nil -} - -// ipv6State maintains state about an IPv6 connection. -type ipv6State struct { - out, in IPv6 -} - -var _ layerState = (*ipv6State)(nil) - -// newIPv6State creates a new ipv6State. -func (n *DUTTestNet) newIPv6State(out, in IPv6) (*ipv6State, error) { - lIP := tcpip.Address(n.LocalIPv6) - rIP := tcpip.Address(n.RemoteIPv6) - s := ipv6State{ - out: IPv6{SrcAddr: &lIP, DstAddr: &rIP}, - in: IPv6{SrcAddr: &rIP, DstAddr: &lIP}, - } - if err := s.out.merge(&out); err != nil { - return nil, err - } - if err := s.in.merge(&in); err != nil { - return nil, err - } - return &s, nil -} - -// outgoing returns an outgoing layer to be sent in a frame. -func (s *ipv6State) outgoing() Layer { - return deepcopy.Copy(&s.out).(Layer) -} - -func (s *ipv6State) incoming(Layer) Layer { - return deepcopy.Copy(&s.in).(Layer) -} - -func (s *ipv6State) sent(Layer) error { - // Nothing to do. - return nil -} - -func (s *ipv6State) received(Layer) error { - // Nothing to do. - return nil -} - -// close cleans up any resources held. -func (s *ipv6State) close() error { - return nil -} - -// tcpState maintains state about a TCP connection. -type tcpState struct { - out, in TCP - localSeqNum, remoteSeqNum *seqnum.Value - synAck *TCP - portPickerFD int - finSent bool -} - -var _ layerState = (*tcpState)(nil) - -// SeqNumValue is a helper routine that allocates a new seqnum.Value value to -// store v and returns a pointer to it. -func SeqNumValue(v seqnum.Value) *seqnum.Value { - return &v -} - -// newTCPState creates a new TCPState. -func (n *DUTTestNet) newTCPState(domain int, out, in TCP) (*tcpState, error) { - portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_STREAM) - if err != nil { - return nil, err - } - s := tcpState{ - out: TCP{SrcPort: &localPort}, - in: TCP{DstPort: &localPort}, - localSeqNum: SeqNumValue(seqnum.Value(rand.Uint32())), - portPickerFD: portPickerFD, - finSent: false, - } - if err := s.out.merge(&out); err != nil { - return nil, err - } - if err := s.in.merge(&in); err != nil { - return nil, err - } - return &s, nil -} - -func (s *tcpState) outgoing() Layer { - newOutgoing := deepcopy.Copy(s.out).(TCP) - if s.localSeqNum != nil { - newOutgoing.SeqNum = Uint32(uint32(*s.localSeqNum)) - } - if s.remoteSeqNum != nil { - newOutgoing.AckNum = Uint32(uint32(*s.remoteSeqNum)) - } - return &newOutgoing -} - -// incoming implements layerState.incoming. -func (s *tcpState) incoming(received Layer) Layer { - tcpReceived, ok := received.(*TCP) - if !ok { - return nil - } - newIn := deepcopy.Copy(s.in).(TCP) - if s.remoteSeqNum != nil { - newIn.SeqNum = Uint32(uint32(*s.remoteSeqNum)) - } - 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(*seq)) - } - return &newIn -} - -func (s *tcpState) sent(sent Layer) error { - tcp, ok := sent.(*TCP) - if !ok { - return fmt.Errorf("can't update tcpState with %T Layer", sent) - } - if !s.finSent { - // update localSeqNum by the payload only when FIN is not yet sent by us - for current := tcp.next(); current != nil; current = current.next() { - s.localSeqNum.UpdateForward(seqnum.Size(current.length())) - } - } - if tcp.Flags != nil && *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 { - s.localSeqNum.UpdateForward(1) - } - if *tcp.Flags&(header.TCPFlagFin) != 0 { - s.finSent = true - } - return nil -} - -func (s *tcpState) received(l Layer) error { - tcp, ok := l.(*TCP) - if !ok { - return fmt.Errorf("can't update tcpState with %T Layer", l) - } - s.remoteSeqNum = SeqNumValue(seqnum.Value(*tcp.SeqNum)) - if *tcp.Flags&(header.TCPFlagSyn|header.TCPFlagFin) != 0 { - s.remoteSeqNum.UpdateForward(1) - } - for current := tcp.next(); current != nil; current = current.next() { - s.remoteSeqNum.UpdateForward(seqnum.Size(current.length())) - } - return nil -} - -// close frees the port associated with this connection. -func (s *tcpState) close() error { - if err := unix.Close(s.portPickerFD); err != nil { - return err - } - s.portPickerFD = -1 - return nil -} - -// udpState maintains state about a UDP connection. -type udpState struct { - out, in UDP - portPickerFD int -} - -var _ layerState = (*udpState)(nil) - -// newUDPState creates a new udpState. -func (n *DUTTestNet) newUDPState(domain int, out, in UDP) (*udpState, error) { - portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_DGRAM) - if err != nil { - return nil, fmt.Errorf("picking port: %w", err) - } - s := udpState{ - out: UDP{SrcPort: &localPort}, - in: UDP{DstPort: &localPort}, - portPickerFD: portPickerFD, - } - if err := s.out.merge(&out); err != nil { - return nil, err - } - if err := s.in.merge(&in); err != nil { - return nil, err - } - return &s, nil -} - -func (s *udpState) outgoing() Layer { - return deepcopy.Copy(&s.out).(Layer) -} - -// incoming implements layerState.incoming. -func (s *udpState) incoming(Layer) Layer { - return deepcopy.Copy(&s.in).(Layer) -} - -func (*udpState) sent(l Layer) error { - return nil -} - -func (*udpState) received(l Layer) error { - return nil -} - -// close frees the port associated with this connection. -func (s *udpState) close() error { - if err := unix.Close(s.portPickerFD); err != nil { - return err - } - s.portPickerFD = -1 - return nil -} - -// Connection holds a collection of layer states for maintaining a connection -// along with sockets for sniffer and injecting packets. -type Connection struct { - layerStates []layerState - injector Injector - sniffer Sniffer -} - -// Returns the default incoming frame against which to match. If received is -// longer than layerStates then that may still count as a match. The reverse is -// never a match and nil is returned. -func (conn *Connection) incoming(received Layers) Layers { - if len(received) < len(conn.layerStates) { - return nil - } - in := Layers{} - for i, s := range conn.layerStates { - toMatch := s.incoming(received[i]) - if toMatch == nil { - return nil - } - in = append(in, toMatch) - } - return in -} - -func (conn *Connection) match(override, received Layers) bool { - toMatch := conn.incoming(received) - if toMatch == nil { - return false // Not enough layers in gotLayers for matching. - } - if err := toMatch.merge(override); err != nil { - return false // Failing to merge is not matching. - } - return toMatch.match(received) -} - -// Close frees associated resources held by the Connection. -func (conn *Connection) Close(t *testing.T) { - t.Helper() - - errs := multierr.Combine(conn.sniffer.close(), conn.injector.close()) - for _, s := range conn.layerStates { - if err := s.close(); err != nil { - errs = multierr.Append(errs, fmt.Errorf("unable to close %+v: %s", s, err)) - } - } - if errs != nil { - t.Fatalf("unable to close %+v: %s", conn, errs) - } -} - -// CreateFrame builds a frame for the connection with defaults overridden -// from the innermost layer out, and additionalLayers added after it. -// -// Note that overrideLayers can have a length that is less than the number -// of layers in this connection, and in such cases the innermost layers are -// overridden first. As an example, valid values of overrideLayers for a TCP- -// over-IPv4-over-Ethernet connection are: nil, [TCP], [IPv4, TCP], and -// [Ethernet, IPv4, TCP]. -func (conn *Connection) CreateFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) Layers { - t.Helper() - - var layersToSend Layers - for i, s := range conn.layerStates { - layer := s.outgoing() - // overrideLayers and conn.layerStates have their tails aligned, so - // to find the index we move backwards by the distance i is to the - // end. - if j := len(overrideLayers) - (len(conn.layerStates) - i); j >= 0 { - if err := layer.merge(overrideLayers[j]); err != nil { - t.Fatalf("can't merge %+v into %+v: %s", layer, overrideLayers[j], err) - } - } - layersToSend = append(layersToSend, layer) - } - layersToSend = append(layersToSend, additionalLayers...) - return layersToSend -} - -// SendFrameStateless sends a frame without updating any of the layer states. -// -// This method is useful for sending out-of-band control messages such as -// ICMP packets, where it would not make sense to update the transport layer's -// state using the ICMP header. -func (conn *Connection) SendFrameStateless(t *testing.T, frame Layers) { - t.Helper() - - outBytes, err := frame.ToBytes() - if err != nil { - t.Fatalf("can't build outgoing packet: %s", err) - } - conn.injector.Send(t, outBytes) -} - -// SendFrame sends a frame on the wire and updates the state of all layers. -func (conn *Connection) SendFrame(t *testing.T, frame Layers) { - t.Helper() - - outBytes, err := frame.ToBytes() - if err != nil { - t.Fatalf("can't build outgoing packet: %s", err) - } - conn.injector.Send(t, outBytes) - - // frame might have nil values where the caller wanted to use default values. - // sentFrame will have no nil values in it because it comes from parsing the - // bytes that were actually sent. - sentFrame := parse(parseEther, outBytes) - // Update the state of each layer based on what was sent. - for i, s := range conn.layerStates { - if err := s.sent(sentFrame[i]); err != nil { - t.Fatalf("Unable to update the state of %+v with %s: %s", s, sentFrame[i], err) - } - } -} - -// send sends a packet, possibly with layers of this connection overridden and -// additional layers added. -// -// Types defined with Connection as the underlying type should expose -// type-safe versions of this method. -func (conn *Connection) send(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) { - t.Helper() - - conn.SendFrame(t, conn.CreateFrame(t, overrideLayers, additionalLayers...)) -} - -// recvFrame gets the next successfully parsed frame (of type Layers) within the -// timeout provided. If no parsable frame arrives before the timeout, it returns -// nil. -func (conn *Connection) recvFrame(t *testing.T, timeout time.Duration) Layers { - t.Helper() - - if timeout <= 0 { - return nil - } - b := conn.sniffer.Recv(t, timeout) - if b == nil { - return nil - } - return parse(parseEther, b) -} - -// layersError stores the Layers that we got and the Layers that we wanted to -// match. -type layersError struct { - got, want Layers -} - -func (e *layersError) Error() string { - return e.got.diff(e.want) -} - -// Expect expects a frame with the final layerStates layer matching the -// provided Layer within the timeout specified. If it doesn't arrive in time, -// an error is returned. -func (conn *Connection) Expect(t *testing.T, layer Layer, timeout time.Duration) (Layer, error) { - t.Helper() - - // Make a frame that will ignore all but the final layer. - layers := make([]Layer, len(conn.layerStates)) - layers[len(layers)-1] = layer - - gotFrame, err := conn.ExpectFrame(t, layers, timeout) - if err != nil { - return nil, err - } - if len(conn.layerStates)-1 < len(gotFrame) { - return gotFrame[len(conn.layerStates)-1], nil - } - t.Fatalf("the received frame should be at least as long as the expected layers, got %d layers, want at least %d layers, got frame: %#v", len(gotFrame), len(conn.layerStates), gotFrame) - panic("unreachable") -} - -// ExpectFrame expects a frame that matches the provided Layers within the -// timeout specified. If one arrives in time, the Layers is returned without an -// error. If it doesn't arrive in time, it returns nil and error is non-nil. -func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Duration) (Layers, error) { - t.Helper() - - deadline := time.Now().Add(timeout) - var errs error - for { - var gotLayers Layers - if timeout := time.Until(deadline); timeout > 0 { - gotLayers = conn.recvFrame(t, timeout) - } - if gotLayers == nil { - if errs == nil { - return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout) - } - return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout) - } - if conn.match(layers, gotLayers) { - for i, s := range conn.layerStates { - if err := s.received(gotLayers[i]); err != nil { - t.Fatalf("failed to update test connection's layer states based on received frame: %s", err) - } - } - return gotLayers, nil - } - want := conn.incoming(layers) - if err := want.merge(layers); err != nil { - errs = multierr.Combine(errs, err) - } else { - errs = multierr.Combine(errs, &layersError{got: gotLayers, want: want}) - } - } -} - -// Drain drains the sniffer's receive buffer by receiving packets until there's -// nothing else to receive. -func (conn *Connection) Drain(t *testing.T) { - t.Helper() - - conn.sniffer.Drain(t) -} - -// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection. -type TCPIPv4 struct { - Connection -} - -// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults. -func (n *DUTTestNet) NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 { - t.Helper() - - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make etherState: %s", err) - } - ipv4State, err := n.newIPv4State(IPv4{}, IPv4{}) - if err != nil { - t.Fatalf("can't make ipv4State: %s", err) - } - tcpState, err := n.newTCPState(unix.AF_INET, outgoingTCP, incomingTCP) - if err != nil { - t.Fatalf("can't make tcpState: %s", err) - } - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - - return TCPIPv4{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv4State, tcpState}, - injector: injector, - sniffer: sniffer, - }, - } -} - -// Connect performs a TCP 3-way handshake. The input Connection should have a -// final TCP Layer. -func (conn *TCPIPv4) Connect(t *testing.T) { - t.Helper() - - // Send the SYN. - conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn)}) - - // Wait for the SYN-ACK. - synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("didn't get synack during handshake: %s", err) - } - conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck - - // Send an ACK. - conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)}) -} - -// ConnectWithOptions performs a TCP 3-way handshake with given TCP options. -// The input Connection should have a final TCP Layer. -func (conn *TCPIPv4) ConnectWithOptions(t *testing.T, options []byte) { - t.Helper() - - // Send the SYN. - conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn), Options: options}) - - // Wait for the SYN-ACK. - synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("didn't get synack during handshake: %s", err) - } - conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck - - // Send an ACK. - conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)}) -} - -// ExpectData is a convenient method that expects a Layer and the Layer after -// it. If it doesn't arrive in time, it returns nil. -func (conn *TCPIPv4) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) { - t.Helper() - - expected := make([]Layer, len(conn.layerStates)) - expected[len(expected)-1] = tcp - if payload != nil { - expected = append(expected, payload) - } - return conn.ExpectFrame(t, expected, timeout) -} - -// ExpectNextData attempts to receive the next incoming segment for the -// connection and expects that to match the given layers. -// -// It differs from ExpectData() in that here we are only interested in the next -// received segment, while ExpectData() can receive multiple segments for the -// connection until there is a match with given layers or a timeout. -func (conn *TCPIPv4) ExpectNextData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) { - t.Helper() - - // Receive the first incoming TCP segment for this connection. - got, err := conn.ExpectData(t, &TCP{}, nil, timeout) - if err != nil { - return nil, err - } - - expected := make([]Layer, len(conn.layerStates)) - expected[len(expected)-1] = tcp - if payload != nil { - expected = append(expected, payload) - tcp.SeqNum = Uint32(uint32(*conn.RemoteSeqNum(t)) - uint32(payload.Length())) - } - if !conn.match(expected, got) { - return nil, fmt.Errorf("next frame is not matching %s during %s: got %s", expected, timeout, got) - } - return got, nil -} - -// Send a packet with reasonable defaults. Potentially override the TCP layer in -// the connection with the provided layer and add additionLayers. -func (conn *TCPIPv4) Send(t *testing.T, tcp TCP, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&tcp}, additionalLayers...) -} - -// Expect expects a frame with the TCP layer matching the provided TCP within -// the timeout specified. If it doesn't arrive in time, an error is returned. -func (conn *TCPIPv4) Expect(t *testing.T, tcp TCP, timeout time.Duration) (*TCP, error) { - t.Helper() - - layer, err := conn.Connection.Expect(t, &tcp, timeout) - if layer == nil { - return nil, err - } - gotTCP, ok := layer.(*TCP) - if !ok { - t.Fatalf("expected %s to be TCP", layer) - } - return gotTCP, err -} - -func (conn *TCPIPv4) tcpState(t *testing.T) *tcpState { - t.Helper() - - state, ok := conn.layerStates[2].(*tcpState) - if !ok { - t.Fatalf("got transport-layer state type=%T, expected tcpState", conn.layerStates[2]) - } - return state -} - -func (conn *TCPIPv4) ipv4State(t *testing.T) *ipv4State { - t.Helper() - - state, ok := conn.layerStates[1].(*ipv4State) - if !ok { - t.Fatalf("expected network-layer state type=%T, expected ipv4State", conn.layerStates[1]) - } - return state -} - -// RemoteSeqNum returns the next expected sequence number from the DUT. -func (conn *TCPIPv4) RemoteSeqNum(t *testing.T) *seqnum.Value { - t.Helper() - - return conn.tcpState(t).remoteSeqNum -} - -// LocalSeqNum returns the next sequence number to send from the testbench. -func (conn *TCPIPv4) LocalSeqNum(t *testing.T) *seqnum.Value { - t.Helper() - - return conn.tcpState(t).localSeqNum -} - -// SynAck returns the SynAck that was part of the handshake. -func (conn *TCPIPv4) SynAck(t *testing.T) *TCP { - t.Helper() - - return conn.tcpState(t).synAck -} - -// LocalAddr gets the local socket address of this connection. -func (conn *TCPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 { - t.Helper() - - sa := &unix.SockaddrInet4{Port: int(*conn.tcpState(t).out.SrcPort)} - copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr) - return sa -} - -// GenerateOTWSeqSegment generates a segment with -// seqnum = RCV.NXT + RCV.WND + seqNumOffset, the generated segment is only -// acceptable when seqNumOffset is 0, otherwise an ACK is expected from the -// receiver. -func GenerateOTWSeqSegment(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP { - t.Helper() - lastAcceptable := conn.LocalSeqNum(t).Add(windowSize) - otwSeq := uint32(lastAcceptable.Add(seqNumOffset)) - return TCP{SeqNum: Uint32(otwSeq), Flags: TCPFlags(header.TCPFlagAck)} -} - -// GenerateUnaccACKSegment generates a segment with -// acknum = SND.NXT + seqNumOffset, the generated segment is only acceptable -// when seqNumOffset is 0, otherwise an ACK is expected from the receiver. -func GenerateUnaccACKSegment(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP { - t.Helper() - lastAcceptable := conn.RemoteSeqNum(t) - unaccAck := uint32(lastAcceptable.Add(seqNumOffset)) - return TCP{AckNum: Uint32(unaccAck), Flags: TCPFlags(header.TCPFlagAck)} -} - -// IPv4Conn maintains the state for all the layers in a IPv4 connection. -type IPv4Conn struct { - Connection -} - -// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults. -func (n *DUTTestNet) NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn { - t.Helper() - - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make EtherState: %s", err) - } - ipv4State, err := n.newIPv4State(outgoingIPv4, incomingIPv4) - if err != nil { - t.Fatalf("can't make IPv4State: %s", err) - } - - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - - return IPv4Conn{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv4State}, - injector: injector, - sniffer: sniffer, - }, - } -} - -// Send sends a frame with ipv4 overriding the IPv4 layer defaults and -// additionalLayers added after it. -func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) { - t.Helper() - - c.send(t, Layers{&ipv4}, additionalLayers...) -} - -// IPv6Conn maintains the state for all the layers in a IPv6 connection. -type IPv6Conn struct { - Connection -} - -// NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults. -func (n *DUTTestNet) NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn { - t.Helper() - - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make EtherState: %s", err) - } - ipv6State, err := n.newIPv6State(outgoingIPv6, incomingIPv6) - if err != nil { - t.Fatalf("can't make IPv6State: %s", err) - } - - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - - return IPv6Conn{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv6State}, - injector: injector, - sniffer: sniffer, - }, - } -} - -// Send sends a frame with ipv6 overriding the IPv6 layer defaults and -// additionalLayers added after it. -func (conn *IPv6Conn) Send(t *testing.T, ipv6 IPv6, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&ipv6}, additionalLayers...) -} - -// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection. -type UDPIPv4 struct { - Connection -} - -// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults. -func (n *DUTTestNet) NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 { - t.Helper() - - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make etherState: %s", err) - } - ipv4State, err := n.newIPv4State(IPv4{}, IPv4{}) - if err != nil { - t.Fatalf("can't make ipv4State: %s", err) - } - udpState, err := n.newUDPState(unix.AF_INET, outgoingUDP, incomingUDP) - if err != nil { - t.Fatalf("can't make udpState: %s", err) - } - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - - return UDPIPv4{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv4State, udpState}, - injector: injector, - sniffer: sniffer, - }, - } -} - -func (conn *UDPIPv4) udpState(t *testing.T) *udpState { - t.Helper() - - state, ok := conn.layerStates[2].(*udpState) - if !ok { - t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2]) - } - return state -} - -func (conn *UDPIPv4) ipv4State(t *testing.T) *ipv4State { - t.Helper() - - state, ok := conn.layerStates[1].(*ipv4State) - if !ok { - t.Fatalf("got network-layer state type=%T, expected ipv4State", conn.layerStates[1]) - } - return state -} - -// LocalAddr gets the local socket address of this connection. -func (conn *UDPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 { - t.Helper() - - sa := &unix.SockaddrInet4{Port: int(*conn.udpState(t).out.SrcPort)} - copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr) - return sa -} - -// SrcPort returns the source port of this connection. -func (conn *UDPIPv4) SrcPort(t *testing.T) uint16 { - t.Helper() - - return *conn.udpState(t).out.SrcPort -} - -// Send sends a packet with reasonable defaults, potentially overriding the UDP -// layer and adding additionLayers. -func (conn *UDPIPv4) Send(t *testing.T, udp UDP, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&udp}, additionalLayers...) -} - -// SendIP sends a packet with reasonable defaults, potentially overriding the -// UDP and IPv4 headers and adding additionLayers. -func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&ip, &udp}, additionalLayers...) -} - -// SendFrame sends a frame on the wire and updates the state of all layers. -func (conn *UDPIPv4) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) { - conn.send(t, overrideLayers, additionalLayers...) -} - -// Expect expects a frame with the UDP layer matching the provided UDP within -// the timeout specified. If it doesn't arrive in time, an error is returned. -func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) { - t.Helper() - - layer, err := conn.Connection.Expect(t, &udp, timeout) - if err != nil { - return nil, err - } - gotUDP, ok := layer.(*UDP) - if !ok { - t.Fatalf("expected %s to be UDP", layer) - } - return gotUDP, nil -} - -// ExpectData is a convenient method that expects a Layer and the Layer after -// it. If it doesn't arrive in time, it returns nil. -func (conn *UDPIPv4) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) { - t.Helper() - - expected := make([]Layer, len(conn.layerStates)) - expected[len(expected)-1] = &udp - if payload.length() != 0 { - expected = append(expected, &payload) - } - return conn.ExpectFrame(t, expected, timeout) -} - -// UDPIPv6 maintains the state for all the layers in a UDP/IPv6 connection. -type UDPIPv6 struct { - Connection -} - -// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults. -func (n *DUTTestNet) NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 { - t.Helper() - - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make etherState: %s", err) - } - ipv6State, err := n.newIPv6State(IPv6{}, IPv6{}) - if err != nil { - t.Fatalf("can't make IPv6State: %s", err) - } - udpState, err := n.newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP) - if err != nil { - t.Fatalf("can't make udpState: %s", err) - } - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - return UDPIPv6{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv6State, udpState}, - injector: injector, - sniffer: sniffer, - }, - } -} - -func (conn *UDPIPv6) udpState(t *testing.T) *udpState { - t.Helper() - - state, ok := conn.layerStates[2].(*udpState) - if !ok { - t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2]) - } - return state -} - -func (conn *UDPIPv6) ipv6State(t *testing.T) *ipv6State { - t.Helper() - - state, ok := conn.layerStates[1].(*ipv6State) - if !ok { - t.Fatalf("got network-layer state type=%T, expected ipv6State", conn.layerStates[1]) - } - return state -} - -// LocalAddr gets the local socket address of this connection. -func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *unix.SockaddrInet6 { - t.Helper() - - sa := &unix.SockaddrInet6{ - Port: int(*conn.udpState(t).out.SrcPort), - // Local address is in perspective to the remote host, so it's scoped to the - // ID of the remote interface. - ZoneId: zoneID, - } - copy(sa.Addr[:], *conn.ipv6State(t).out.SrcAddr) - return sa -} - -// SrcPort returns the source port of this connection. -func (conn *UDPIPv6) SrcPort(t *testing.T) uint16 { - t.Helper() - - return *conn.udpState(t).out.SrcPort -} - -// Send sends a packet with reasonable defaults, potentially overriding the UDP -// layer and adding additionLayers. -func (conn *UDPIPv6) Send(t *testing.T, udp UDP, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&udp}, additionalLayers...) -} - -// SendIPv6 sends a packet with reasonable defaults, potentially overriding the -// UDP and IPv6 headers and adding additionLayers. -func (conn *UDPIPv6) SendIPv6(t *testing.T, ip IPv6, udp UDP, additionalLayers ...Layer) { - t.Helper() - - conn.send(t, Layers{&ip, &udp}, additionalLayers...) -} - -// SendFrame sends a frame on the wire and updates the state of all layers. -func (conn *UDPIPv6) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) { - conn.send(t, overrideLayers, additionalLayers...) -} - -// Expect expects a frame with the UDP layer matching the provided UDP within -// the timeout specified. If it doesn't arrive in time, an error is returned. -func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) { - t.Helper() - - layer, err := conn.Connection.Expect(t, &udp, timeout) - if err != nil { - return nil, err - } - gotUDP, ok := layer.(*UDP) - if !ok { - t.Fatalf("expected %s to be UDP", layer) - } - return gotUDP, nil -} - -// ExpectData is a convenient method that expects a Layer and the Layer after -// it. If it doesn't arrive in time, it returns nil. -func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) { - t.Helper() - - expected := make([]Layer, len(conn.layerStates)) - expected[len(expected)-1] = &udp - if payload.length() != 0 { - expected = append(expected, &payload) - } - return conn.ExpectFrame(t, expected, timeout) -} - -// TCPIPv6 maintains the state for all the layers in a TCP/IPv6 connection. -type TCPIPv6 struct { - Connection -} - -// NewTCPIPv6 creates a new TCPIPv6 connection with reasonable defaults. -func (n *DUTTestNet) NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 { - etherState, err := n.newEtherState(Ether{}, Ether{}) - if err != nil { - t.Fatalf("can't make etherState: %s", err) - } - ipv6State, err := n.newIPv6State(IPv6{}, IPv6{}) - if err != nil { - t.Fatalf("can't make ipv6State: %s", err) - } - tcpState, err := n.newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP) - if err != nil { - t.Fatalf("can't make tcpState: %s", err) - } - injector, err := n.NewInjector(t) - if err != nil { - t.Fatalf("can't make injector: %s", err) - } - sniffer, err := n.NewSniffer(t) - if err != nil { - t.Fatalf("can't make sniffer: %s", err) - } - - return TCPIPv6{ - Connection: Connection{ - layerStates: []layerState{etherState, ipv6State, tcpState}, - injector: injector, - sniffer: sniffer, - }, - } -} - -// SrcPort returns the source port from the given Connection. -func (conn *TCPIPv6) SrcPort() uint16 { - state := conn.layerStates[2].(*tcpState) - return *state.out.SrcPort -} - -// ExpectData is a convenient method that expects a Layer and the Layer after -// it. If it doesn't arrive in time, it returns nil. -func (conn *TCPIPv6) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) { - t.Helper() - - expected := make([]Layer, len(conn.layerStates)) - expected[len(expected)-1] = tcp - if payload != nil { - expected = append(expected, payload) - } - return conn.ExpectFrame(t, expected, timeout) -} diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go deleted file mode 100644 index eabdc8cb3..000000000 --- a/test/packetimpact/testbench/dut.go +++ /dev/null @@ -1,803 +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 testbench - -import ( - "context" - "encoding/binary" - "fmt" - "net" - "testing" - "time" - - pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto" - - "golang.org/x/sys/unix" - "google.golang.org/grpc" - "google.golang.org/grpc/keepalive" -) - -// DUT communicates with the DUT to force it to make POSIX calls. -type DUT struct { - conn *grpc.ClientConn - posixServer POSIXClient - Net *DUTTestNet - Uname *DUTUname -} - -// NewDUT creates a new connection with the DUT over gRPC. -func NewDUT(t *testing.T) DUT { - t.Helper() - info := getDUTInfo() - dut := info.ConnectToDUT(t) - t.Cleanup(func() { - dut.TearDownConnection() - info.release() - }) - return dut -} - -// ConnectToDUT connects to DUT through gRPC. -func (info *DUTInfo) ConnectToDUT(t *testing.T) DUT { - t.Helper() - - n := info.Net - posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.POSIXServerPort)) - conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive})) - if err != nil { - t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err) - } - posixServer := NewPOSIXClient(conn) - return DUT{ - conn: conn, - posixServer: posixServer, - Net: n, - Uname: info.Uname, - } -} - -// TearDownConnection closes the underlying connection. -func (dut *DUT) TearDownConnection() { - dut.conn.Close() -} - -func (dut *DUT) sockaddrToProto(t *testing.T, sa unix.Sockaddr) *pb.Sockaddr { - t.Helper() - - switch s := sa.(type) { - case *unix.SockaddrInet4: - return &pb.Sockaddr{ - Sockaddr: &pb.Sockaddr_In{ - In: &pb.SockaddrIn{ - Family: unix.AF_INET, - Port: uint32(s.Port), - Addr: s.Addr[:], - }, - }, - } - case *unix.SockaddrInet6: - return &pb.Sockaddr{ - Sockaddr: &pb.Sockaddr_In6{ - In6: &pb.SockaddrIn6{ - Family: unix.AF_INET6, - Port: uint32(s.Port), - Flowinfo: 0, - ScopeId: s.ZoneId, - Addr: s.Addr[:], - }, - }, - } - } - t.Fatalf("can't parse Sockaddr struct: %+v", sa) - return nil -} - -func (dut *DUT) protoToSockaddr(t *testing.T, sa *pb.Sockaddr) unix.Sockaddr { - t.Helper() - - switch s := sa.Sockaddr.(type) { - case *pb.Sockaddr_In: - ret := unix.SockaddrInet4{ - Port: int(s.In.GetPort()), - } - copy(ret.Addr[:], s.In.GetAddr()) - return &ret - case *pb.Sockaddr_In6: - ret := unix.SockaddrInet6{ - Port: int(s.In6.GetPort()), - ZoneId: s.In6.GetScopeId(), - } - copy(ret.Addr[:], s.In6.GetAddr()) - return &ret - } - t.Fatalf("can't parse Sockaddr proto: %#v", sa) - return nil -} - -// CreateBoundSocket makes a new socket on the DUT, with type typ and protocol -// proto, and bound to the IP address addr. Returns the new file descriptor and -// the port that was selected on the DUT. -func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) (int32, uint16) { - t.Helper() - - var fd int32 - if addr.To4() != nil { - fd = dut.Socket(t, unix.AF_INET, typ, proto) - sa := unix.SockaddrInet4{} - copy(sa.Addr[:], addr.To4()) - dut.Bind(t, fd, &sa) - } else if addr.To16() != nil { - fd = dut.Socket(t, unix.AF_INET6, typ, proto) - sa := unix.SockaddrInet6{} - copy(sa.Addr[:], addr.To16()) - sa.ZoneId = dut.Net.RemoteDevID - dut.Bind(t, fd, &sa) - } else { - t.Fatalf("invalid IP address: %s", addr) - } - sa := dut.GetSockName(t, fd) - var port int - switch s := sa.(type) { - case *unix.SockaddrInet4: - port = s.Port - case *unix.SockaddrInet6: - port = s.Port - default: - t.Fatalf("unknown sockaddr type from getsockname: %T", sa) - } - return fd, uint16(port) -} - -// CreateListener makes a new TCP connection. If it fails, the test ends. -func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32, uint16) { - t.Helper() - - fd, remotePort := dut.CreateBoundSocket(t, typ, proto, dut.Net.RemoteIPv4) - dut.Listen(t, fd, backlog) - return fd, remotePort -} - -// All the functions that make gRPC calls to the POSIX service are below, sorted -// alphabetically. - -// Accept calls accept on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// AcceptWithErrno. -func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - fd, sa, err := dut.AcceptWithErrno(ctx, t, sockfd) - if fd < 0 { - t.Fatalf("failed to accept: %s", err) - } - return fd, sa -} - -// AcceptWithErrno calls accept on the DUT. -func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { - t.Helper() - - req := &pb.AcceptRequest{ - Sockfd: sockfd, - } - resp, err := dut.posixServer.Accept(ctx, req) - if err != nil { - t.Fatalf("failed to call Accept: %s", err) - } - return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), unix.Errno(resp.GetErrno_()) -} - -// Bind calls bind on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is -// needed, use BindWithErrno. -func (dut *DUT) Bind(t *testing.T, fd int32, sa unix.Sockaddr) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.BindWithErrno(ctx, t, fd, sa) - if ret != 0 { - t.Fatalf("failed to bind socket: %s", err) - } -} - -// BindWithErrno calls bind on the DUT. -func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { - t.Helper() - - req := &pb.BindRequest{ - Sockfd: fd, - Addr: dut.sockaddrToProto(t, sa), - } - resp, err := dut.posixServer.Bind(ctx, req) - if err != nil { - t.Fatalf("failed to call Bind: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// Close calls close on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// CloseWithErrno. -func (dut *DUT) Close(t *testing.T, fd int32) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.CloseWithErrno(ctx, t, fd) - if ret != 0 { - t.Fatalf("failed to close: %s", err) - } -} - -// CloseWithErrno calls close on the DUT. -func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int32, error) { - t.Helper() - - req := &pb.CloseRequest{ - Fd: fd, - } - resp, err := dut.posixServer.Close(ctx, req) - if err != nil { - t.Fatalf("failed to call Close: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// Connect calls connect on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use ConnectWithErrno. -func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ConnectWithErrno(ctx, t, fd, sa) - // Ignore 'operation in progress' error that can be returned when the socket - // is non-blocking. - if err != unix.EINPROGRESS && ret != 0 { - t.Fatalf("failed to connect socket: %s", err) - } -} - -// ConnectWithErrno calls bind on the DUT. -func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) { - t.Helper() - - req := &pb.ConnectRequest{ - Sockfd: fd, - Addr: dut.sockaddrToProto(t, sa), - } - resp, err := dut.posixServer.Connect(ctx, req) - if err != nil { - t.Fatalf("failed to call Connect: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// GetSockName calls getsockname on the DUT and causes a fatal test failure if -// it doesn't succeed. If more control over the timeout or error handling is -// needed, use GetSockNameWithErrno. -func (dut *DUT) GetSockName(t *testing.T, sockfd int32) unix.Sockaddr { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, sa, err := dut.GetSockNameWithErrno(ctx, t, sockfd) - if ret != 0 { - t.Fatalf("failed to getsockname: %s", err) - } - return sa -} - -// GetSockNameWithErrno calls getsockname on the DUT. -func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) { - t.Helper() - - req := &pb.GetSockNameRequest{ - Sockfd: sockfd, - } - resp, err := dut.posixServer.GetSockName(ctx, req) - if err != nil { - t.Fatalf("failed to call Bind: %s", err) - } - return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), unix.Errno(resp.GetErrno_()) -} - -func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) { - t.Helper() - - req := &pb.GetSockOptRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Optlen: optlen, - Type: typ, - } - resp, err := dut.posixServer.GetSockOpt(ctx, req) - if err != nil { - t.Fatalf("failed to call GetSockOpt: %s", err) - } - optval := resp.GetOptval() - if optval == nil { - t.Fatalf("GetSockOpt response does not contain a value") - } - return resp.GetRet(), optval, unix.Errno(resp.GetErrno_()) -} - -// GetSockOpt calls getsockopt on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use GetSockOptWithErrno. Because endianess and the width of values -// might differ between the testbench and DUT architectures, prefer to use a -// more specific GetSockOptXxx function. -func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, optval, err := dut.GetSockOptWithErrno(ctx, t, sockfd, level, optname, optlen) - if ret != 0 { - t.Fatalf("failed to GetSockOpt: %s", err) - } - return optval -} - -// GetSockOptWithErrno calls getsockopt on the DUT. Because endianess and the -// width of values might differ between the testbench and DUT architectures, -// prefer to use a more specific GetSockOptXxxWithErrno function. -func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32) (int32, []byte, error) { - t.Helper() - - ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES) - bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval) - if !ok { - t.Fatalf("GetSockOpt got value type: %T, want bytes", optval.Val) - } - return ret, bytesval.Bytesval, errno -} - -// GetSockOptInt calls getsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the int optval or error handling -// is needed, use GetSockOptIntWithErrno. -func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, intval, err := dut.GetSockOptIntWithErrno(ctx, t, sockfd, level, optname) - if ret != 0 { - t.Fatalf("failed to GetSockOptInt: %s", err) - } - return intval -} - -// GetSockOptIntWithErrno calls getsockopt with an integer optval. -func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, int32, error) { - t.Helper() - - ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_INT) - intval, ok := optval.Val.(*pb.SockOptVal_Intval) - if !ok { - t.Fatalf("GetSockOpt got value type: %T, want int", optval.Val) - } - return ret, intval.Intval, errno -} - -// GetSockOptTimeval calls getsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the timeout or error handling is -// needed, use GetSockOptTimevalWithErrno. -func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname) - if ret != 0 { - t.Fatalf("failed to GetSockOptTimeval: %s", err) - } - return timeval -} - -// GetSockOptTimevalWithErrno calls getsockopt and returns a timeval. -func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, unix.Timeval, error) { - t.Helper() - - ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME) - tv, ok := optval.Val.(*pb.SockOptVal_Timeval) - if !ok { - t.Fatalf("GetSockOpt got value type: %T, want timeval", optval.Val) - } - timeval := unix.Timeval{ - Sec: tv.Timeval.Seconds, - Usec: tv.Timeval.Microseconds, - } - return ret, timeval, errno -} - -// Listen calls listen on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// ListenWithErrno. -func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ListenWithErrno(ctx, t, sockfd, backlog) - if ret != 0 { - t.Fatalf("failed to listen: %s", err) - } -} - -// ListenWithErrno calls listen on the DUT. -func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backlog int32) (int32, error) { - t.Helper() - - req := &pb.ListenRequest{ - Sockfd: sockfd, - Backlog: backlog, - } - resp, err := dut.posixServer.Listen(ctx, req) - if err != nil { - t.Fatalf("failed to call Listen: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// PollOne calls poll on the DUT and asserts that the expected event must be -// signaled on the given fd within the given timeout. -func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Duration) { - t.Helper() - - pfds := dut.Poll(t, []unix.PollFd{{Fd: fd, Events: events}}, timeout) - if n := len(pfds); n != 1 { - t.Fatalf("Poll returned %d ready file descriptors, expected 1", n) - } - if readyFd := pfds[0].Fd; readyFd != fd { - t.Fatalf("Poll returned an fd %d that was not requested (%d)", readyFd, fd) - } - if got, want := pfds[0].Revents, int16(events); got&want == 0 { - t.Fatalf("Poll returned no events in our interest, got: %#b, want: %#b", got, want) - } -} - -// Poll calls poll on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over error handling is needed, use PollWithErrno. -// Only pollfds with non-empty revents are returned, the only way to tie the -// response back to the original request is using the fd number. -func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd { - t.Helper() - - ctx := context.Background() - var cancel context.CancelFunc - if timeout >= 0 { - ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout) - defer cancel() - } - ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout) - if ret < 0 { - t.Fatalf("failed to poll: %s", err) - } - return result -} - -// PollWithErrno calls poll on the DUT. -func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) { - t.Helper() - - req := &pb.PollRequest{ - TimeoutMillis: int32(timeout.Milliseconds()), - } - for _, pfd := range pfds { - req.Pfds = append(req.Pfds, &pb.PollFd{ - Fd: pfd.Fd, - Events: uint32(pfd.Events), - }) - } - resp, err := dut.posixServer.Poll(ctx, req) - if err != nil { - t.Fatalf("failed to call Poll: %s", err) - } - if ret, npfds := resp.GetRet(), len(resp.GetPfds()); ret >= 0 && int(ret) != npfds { - t.Fatalf("nonsensical poll response: ret(%d) != len(pfds)(%d)", ret, npfds) - } - var result []unix.PollFd - for _, protoPfd := range resp.GetPfds() { - result = append(result, unix.PollFd{ - Fd: protoPfd.GetFd(), - Revents: int16(protoPfd.GetEvents()), - }) - } - return resp.GetRet(), result, unix.Errno(resp.GetErrno_()) -} - -// Send calls send on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// SendWithErrno. -func (dut *DUT) Send(t *testing.T, sockfd int32, buf []byte, flags int32) int32 { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SendWithErrno(ctx, t, sockfd, buf, flags) - if ret == -1 { - t.Fatalf("failed to send: %s", err) - } - return ret -} - -// SendWithErrno calls send on the DUT. -func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32) (int32, error) { - t.Helper() - - req := &pb.SendRequest{ - Sockfd: sockfd, - Buf: buf, - Flags: flags, - } - resp, err := dut.posixServer.Send(ctx, req) - if err != nil { - t.Fatalf("failed to call Send: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// SendTo calls sendto on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// SendToWithErrno. -func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SendToWithErrno(ctx, t, sockfd, buf, flags, destAddr) - if ret == -1 { - t.Fatalf("failed to sendto: %s", err) - } - return ret -} - -// SendToWithErrno calls sendto on the DUT. -func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) { - t.Helper() - - req := &pb.SendToRequest{ - Sockfd: sockfd, - Buf: buf, - Flags: flags, - DestAddr: dut.sockaddrToProto(t, destAddr), - } - resp, err := dut.posixServer.SendTo(ctx, req) - if err != nil { - t.Fatalf("failed to call SendTo: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking -// is true, otherwise it will clear the flag. -func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) { - t.Helper() - - req := &pb.SetNonblockingRequest{ - Fd: fd, - Nonblocking: nonblocking, - } - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - - resp, err := dut.posixServer.SetNonblocking(ctx, req) - if err != nil { - t.Fatalf("failed to call SetNonblocking: %s", err) - } - if resp.GetRet() == -1 { - t.Fatalf("fcntl(%d, %s) failed: %s", fd, resp.GetCmd(), unix.Errno(resp.GetErrno_())) - } -} - -func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) { - t.Helper() - - req := &pb.SetSockOptRequest{ - Sockfd: sockfd, - Level: level, - Optname: optname, - Optval: optval, - } - resp, err := dut.posixServer.SetSockOpt(ctx, req) - if err != nil { - t.Fatalf("failed to call SetSockOpt: %s", err) - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} - -// SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use SetSockOptWithErrno. Because endianess and the width of values -// might differ between the testbench and DUT architectures, prefer to use a -// more specific SetSockOptXxx function. -func (dut *DUT) SetSockOpt(t *testing.T, sockfd, level, optname int32, optval []byte) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptWithErrno(ctx, t, sockfd, level, optname, optval) - if ret != 0 { - t.Fatalf("failed to SetSockOpt: %s", err) - } -} - -// SetSockOptWithErrno calls setsockopt on the DUT. Because endianess and the -// width of values might differ between the testbench and DUT architectures, -// prefer to use a more specific SetSockOptXxxWithErrno function. -func (dut *DUT) SetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval []byte) (int32, error) { - t.Helper() - - return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}}) -} - -// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the int optval or error handling -// is needed, use SetSockOptIntWithErrno. -func (dut *DUT) SetSockOptInt(t *testing.T, sockfd, level, optname, optval int32) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptIntWithErrno(ctx, t, sockfd, level, optname, optval) - if ret != 0 { - t.Fatalf("failed to SetSockOptInt: %s", err) - } -} - -// SetSockOptIntWithErrno calls setsockopt with an integer optval. -func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optval int32) (int32, error) { - t.Helper() - - return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}}) -} - -// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure -// if it doesn't succeed. If more control over the timeout or error handling is -// needed, use SetSockOptTimevalWithErrno. -func (dut *DUT) SetSockOptTimeval(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.SetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname, tv) - if ret != 0 { - t.Fatalf("failed to SetSockOptTimeval: %s", err) - } -} - -// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to -// bytes. -func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) { - t.Helper() - - timeval := pb.Timeval{ - Seconds: int64(tv.Sec), - Microseconds: int64(tv.Usec), - } - return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}}) -} - -// Socket calls socket on the DUT and returns the file descriptor. If socket -// fails on the DUT, the test ends. -func (dut *DUT) Socket(t *testing.T, domain, typ, proto int32) int32 { - t.Helper() - - fd, err := dut.SocketWithErrno(t, domain, typ, proto) - if fd < 0 { - t.Fatalf("failed to create socket: %s", err) - } - return fd -} - -// SocketWithErrno calls socket on the DUT and returns the fd and errno. -func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, error) { - t.Helper() - - req := &pb.SocketRequest{ - Domain: domain, - Type: typ, - Protocol: proto, - } - ctx := context.Background() - resp, err := dut.posixServer.Socket(ctx, req) - if err != nil { - t.Fatalf("failed to call Socket: %s", err) - } - return resp.GetFd(), unix.Errno(resp.GetErrno_()) -} - -// Recv calls recv on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// RecvWithErrno. -func (dut *DUT) Recv(t *testing.T, sockfd, len, flags int32) []byte { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, buf, err := dut.RecvWithErrno(ctx, t, sockfd, len, flags) - if ret == -1 { - t.Fatalf("failed to recv: %s", err) - } - return buf -} - -// RecvWithErrno calls recv on the DUT. -func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, flags int32) (int32, []byte, error) { - t.Helper() - - req := &pb.RecvRequest{ - Sockfd: sockfd, - Len: len, - Flags: flags, - } - resp, err := dut.posixServer.Recv(ctx, req) - if err != nil { - t.Fatalf("failed to call Recv: %s", err) - } - return resp.GetRet(), resp.GetBuf(), unix.Errno(resp.GetErrno_()) -} - -// SetSockLingerOption sets SO_LINGER socket option on the DUT. -func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Duration, enable bool) { - var linger unix.Linger - if enable { - linger.Onoff = 1 - } - linger.Linger = int32(timeout / time.Second) - - buf := make([]byte, 8) - binary.LittleEndian.PutUint32(buf, uint32(linger.Onoff)) - binary.LittleEndian.PutUint32(buf[4:], uint32(linger.Linger)) - dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf) -} - -// Shutdown calls shutdown on the DUT and causes a fatal test failure if it -// doesn't succeed. If more control over the timeout or error handling is -// needed, use ShutdownWithErrno. -func (dut *DUT) Shutdown(t *testing.T, fd, how int32) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) - defer cancel() - ret, err := dut.ShutdownWithErrno(ctx, t, fd, how) - if ret != 0 { - t.Fatalf("failed to shutdown(%d, %d): %s", fd, how, err) - } -} - -// ShutdownWithErrno calls shutdown on the DUT. -func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) (int32, error) { - t.Helper() - - req := &pb.ShutdownRequest{ - Fd: fd, - How: how, - } - resp, err := dut.posixServer.Shutdown(ctx, req) - if err != nil { - t.Fatalf("failed to call Shutdown: %s", err) - } - if resp.GetErrno_() == 0 { - return resp.GetRet(), nil - } - return resp.GetRet(), unix.Errno(resp.GetErrno_()) -} diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go deleted file mode 100644 index 0fc3d97b4..000000000 --- a/test/packetimpact/testbench/dut_client.go +++ /dev/null @@ -1,28 +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 testbench - -import ( - "google.golang.org/grpc" - pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto" -) - -// POSIXClient is a gRPC client for the Posix service. -type POSIXClient pb.PosixClient - -// NewPOSIXClient makes a new gRPC client for the POSIX service. -func NewPOSIXClient(c grpc.ClientConnInterface) POSIXClient { - return pb.NewPosixClient(c) -} diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go deleted file mode 100644 index 2311f7686..000000000 --- a/test/packetimpact/testbench/layers.go +++ /dev/null @@ -1,1601 +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 testbench - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "go.uber.org/multierr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/buffer" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -// Layer is the interface that all encapsulations must implement. -// -// A Layer is an encapsulation in a packet, such as TCP, IPv4, IPv6, etc. A -// Layer contains all the fields of the encapsulation. Each field is a pointer -// and may be nil. -type Layer interface { - fmt.Stringer - - // ToBytes converts the Layer into bytes. In places where the Layer's field - // isn't nil, the value that is pointed to is used. When the field is nil, a - // reasonable default for the Layer is used. For example, "64" for IPv4 TTL - // and a calculated checksum for TCP or IP. Some layers require information - // from the previous or next layers in order to compute a default, such as - // TCP's checksum or Ethernet's type, so each Layer has a doubly-linked list - // to the layer's neighbors. - ToBytes() ([]byte, error) - - // match checks if the current Layer matches the provided Layer. If either - // Layer has a nil in a given field, that field is considered matching. - // Otherwise, the values pointed to by the fields must match. The LayerBase is - // ignored. - match(Layer) bool - - // length in bytes of the current encapsulation - length() int - - // next gets a pointer to the encapsulated Layer. - next() Layer - - // prev gets a pointer to the Layer encapsulating this one. - Prev() Layer - - // setNext sets the pointer to the encapsulated Layer. - setNext(Layer) - - // setPrev sets the pointer to the Layer encapsulating this one. - setPrev(Layer) - - // merge overrides the values in the interface with the provided values. - merge(Layer) error -} - -// LayerBase is the common elements of all layers. -type LayerBase struct { - nextLayer Layer - prevLayer Layer -} - -func (lb *LayerBase) next() Layer { - return lb.nextLayer -} - -// Prev returns the previous layer. -func (lb *LayerBase) Prev() Layer { - return lb.prevLayer -} - -func (lb *LayerBase) setNext(l Layer) { - lb.nextLayer = l -} - -func (lb *LayerBase) setPrev(l Layer) { - lb.prevLayer = l -} - -// equalLayer compares that two Layer structs match while ignoring field in -// which either input has a nil and also ignoring the LayerBase of the inputs. -func equalLayer(x, y Layer) bool { - if x == nil || y == nil { - return true - } - // opt ignores comparison pairs where either of the inputs is a nil. - opt := cmp.FilterValues(func(x, y interface{}) bool { - for _, l := range []interface{}{x, y} { - v := reflect.ValueOf(l) - if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice) && v.IsNil() { - return true - } - } - return false - }, cmp.Ignore()) - return cmp.Equal(x, y, opt, cmpopts.IgnoreTypes(LayerBase{})) -} - -// mergeLayer merges y into x. Any fields for which y has a non-nil value, that -// value overwrite the corresponding fields in x. -func mergeLayer(x, y Layer) error { - if y == nil { - return nil - } - if reflect.TypeOf(x) != reflect.TypeOf(y) { - return fmt.Errorf("can't merge %T into %T", y, x) - } - vx := reflect.ValueOf(x).Elem() - vy := reflect.ValueOf(y).Elem() - t := vy.Type() - for i := 0; i < vy.NumField(); i++ { - t := t.Field(i) - if t.Anonymous { - // Ignore the LayerBase in the Layer struct. - continue - } - v := vy.Field(i) - if v.IsNil() { - continue - } - vx.Field(i).Set(v) - } - return nil -} - -func stringLayer(l Layer) string { - v := reflect.ValueOf(l).Elem() - t := v.Type() - var ret []string - for i := 0; i < v.NumField(); i++ { - t := t.Field(i) - if t.Anonymous { - // Ignore the LayerBase in the Layer struct. - continue - } - v := v.Field(i) - if v.IsNil() { - continue - } - v = reflect.Indirect(v) - if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 { - ret = append(ret, fmt.Sprintf("%s:\n%v", t.Name, hex.Dump(v.Bytes()))) - } else { - ret = append(ret, fmt.Sprintf("%s:%v", t.Name, v)) - } - } - return fmt.Sprintf("&%s{%s}", t, strings.Join(ret, " ")) -} - -// Ether can construct and match an ethernet encapsulation. -type Ether struct { - LayerBase - SrcAddr *tcpip.LinkAddress - DstAddr *tcpip.LinkAddress - Type *tcpip.NetworkProtocolNumber -} - -func (l *Ether) String() string { - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *Ether) ToBytes() ([]byte, error) { - b := make([]byte, header.EthernetMinimumSize) - h := header.Ethernet(b) - fields := &header.EthernetFields{} - if l.SrcAddr != nil { - fields.SrcAddr = *l.SrcAddr - } - if l.DstAddr != nil { - fields.DstAddr = *l.DstAddr - } - if l.Type != nil { - fields.Type = *l.Type - } else { - switch n := l.next().(type) { - case *IPv4: - fields.Type = header.IPv4ProtocolNumber - case *IPv6: - fields.Type = header.IPv6ProtocolNumber - default: - return nil, fmt.Errorf("ethernet header's next layer is unrecognized: %#v", n) - } - } - h.Encode(fields) - return h, nil -} - -// LinkAddress is a helper routine that allocates a new tcpip.LinkAddress value -// to store v and returns a pointer to it. -func LinkAddress(v tcpip.LinkAddress) *tcpip.LinkAddress { - return &v -} - -// NetworkProtocolNumber is a helper routine that allocates a new -// tcpip.NetworkProtocolNumber value to store v and returns a pointer to it. -func NetworkProtocolNumber(v tcpip.NetworkProtocolNumber) *tcpip.NetworkProtocolNumber { - return &v -} - -// layerParser parses the input bytes and returns a Layer along with the next -// layerParser to run. If there is no more parsing to do, the returned -// layerParser is nil. -type layerParser func([]byte) (Layer, layerParser) - -// parse parses bytes starting with the first layerParser and using successive -// layerParsers until all the bytes are parsed. -func parse(parser layerParser, b []byte) Layers { - var layers Layers - for { - var layer Layer - layer, parser = parser(b) - layers = append(layers, layer) - if parser == nil { - break - } - b = b[layer.length():] - } - layers.linkLayers() - return layers -} - -// parseEther parses the bytes assuming that they start with an ethernet header -// and continues parsing further encapsulations. -func parseEther(b []byte) (Layer, layerParser) { - h := header.Ethernet(b) - ether := Ether{ - SrcAddr: LinkAddress(h.SourceAddress()), - DstAddr: LinkAddress(h.DestinationAddress()), - Type: NetworkProtocolNumber(h.Type()), - } - var nextParser layerParser - switch h.Type() { - case header.IPv4ProtocolNumber: - nextParser = parseIPv4 - case header.IPv6ProtocolNumber: - nextParser = parseIPv6 - default: - // Assume that the rest is a payload. - nextParser = parsePayload - } - return ðer, nextParser -} - -func (l *Ether) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *Ether) length() int { - return header.EthernetMinimumSize -} - -// merge implements Layer.merge. -func (l *Ether) merge(other Layer) error { - return mergeLayer(l, other) -} - -// IPv4 can construct and match an IPv4 encapsulation. -type IPv4 struct { - LayerBase - IHL *uint8 - TOS *uint8 - TotalLength *uint16 - ID *uint16 - Flags *uint8 - FragmentOffset *uint16 - TTL *uint8 - Protocol *uint8 - Checksum *uint16 - SrcAddr *tcpip.Address - DstAddr *tcpip.Address - Options *header.IPv4Options -} - -func (l *IPv4) String() string { - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *IPv4) ToBytes() ([]byte, error) { - // An IPv4 header is variable length depending on the size of the Options. - hdrLen := header.IPv4MinimumSize - if l.Options != nil { - if len(*l.Options)%4 != 0 { - return nil, fmt.Errorf("invalid header options '%x (len=%d)'; must be 32 bit aligned", *l.Options, len(*l.Options)) - } - hdrLen += len(*l.Options) - if hdrLen > header.IPv4MaximumHeaderSize { - return nil, fmt.Errorf("IPv4 Options %d bytes, Max %d", len(*l.Options), header.IPv4MaximumOptionsSize) - } - } - b := make([]byte, hdrLen) - h := header.IPv4(b) - fields := &header.IPv4Fields{ - TOS: 0, - TotalLength: 0, - ID: 0, - Flags: 0, - FragmentOffset: 0, - TTL: 64, - Protocol: 0, - Checksum: 0, - SrcAddr: tcpip.Address(""), - DstAddr: tcpip.Address(""), - Options: nil, - } - if l.TOS != nil { - fields.TOS = *l.TOS - } - if l.TotalLength != nil { - fields.TotalLength = *l.TotalLength - } else { - fields.TotalLength = uint16(l.length()) - current := l.next() - for current != nil { - fields.TotalLength += uint16(current.length()) - current = current.next() - } - } - if l.ID != nil { - fields.ID = *l.ID - } - if l.Flags != nil { - fields.Flags = *l.Flags - } - if l.FragmentOffset != nil { - fields.FragmentOffset = *l.FragmentOffset - } - if l.TTL != nil { - fields.TTL = *l.TTL - } - if l.Protocol != nil { - fields.Protocol = *l.Protocol - } else { - switch n := l.next().(type) { - case *TCP: - fields.Protocol = uint8(header.TCPProtocolNumber) - case *UDP: - fields.Protocol = uint8(header.UDPProtocolNumber) - case *ICMPv4: - fields.Protocol = uint8(header.ICMPv4ProtocolNumber) - default: - // TODO(b/150301488): Support more protocols as needed. - return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n) - } - } - if l.SrcAddr != nil { - fields.SrcAddr = *l.SrcAddr - } - if l.DstAddr != nil { - fields.DstAddr = *l.DstAddr - } - - h.Encode(fields) - - // Put raw option bytes from test definition in header. Options as raw bytes - // allows us to serialize malformed options, which is not possible with - // the provided serialization functions. - if l.Options != nil { - h.SetHeaderLength(h.HeaderLength() + uint8(len(*l.Options))) - if got, want := copy(h.Options(), *l.Options), len(*l.Options); got != want { - return nil, fmt.Errorf("failed to copy option bytes into header, got %d want %d", got, want) - } - } - - // Encode cannot set this incorrectly so we need to overwrite what it wrote - // in order to test handling of a bad IHL value. - if l.IHL != nil { - h.SetHeaderLength(*l.IHL) - } - - if l.Checksum == nil { - h.SetChecksum(^h.CalculateChecksum()) - } else { - h.SetChecksum(*l.Checksum) - } - - return h, nil -} - -// Uint16 is a helper routine that allocates a new -// uint16 value to store v and returns a pointer to it. -func Uint16(v uint16) *uint16 { - return &v -} - -// Uint8 is a helper routine that allocates a new -// uint8 value to store v and returns a pointer to it. -func Uint8(v uint8) *uint8 { - return &v -} - -// TCPFlags is a helper routine that allocates a new -// header.TCPFlags value to store v and returns a pointer to it. -func TCPFlags(v header.TCPFlags) *header.TCPFlags { - return &v -} - -// Address is a helper routine that allocates a new tcpip.Address value to -// store v and returns a pointer to it. -func Address(v tcpip.Address) *tcpip.Address { - return &v -} - -// parseIPv4 parses the bytes assuming that they start with an ipv4 header and -// continues parsing further encapsulations. -func parseIPv4(b []byte) (Layer, layerParser) { - h := header.IPv4(b) - options := h.Options() - tos, _ := h.TOS() - ipv4 := IPv4{ - IHL: Uint8(h.HeaderLength()), - TOS: &tos, - TotalLength: Uint16(h.TotalLength()), - ID: Uint16(h.ID()), - Flags: Uint8(h.Flags()), - FragmentOffset: Uint16(h.FragmentOffset()), - TTL: Uint8(h.TTL()), - Protocol: Uint8(h.Protocol()), - Checksum: Uint16(h.Checksum()), - SrcAddr: Address(h.SourceAddress()), - DstAddr: Address(h.DestinationAddress()), - Options: &options, - } - var nextParser layerParser - // If it is a fragment, don't treat it as having a transport protocol. - if h.FragmentOffset() != 0 || h.More() { - return &ipv4, parsePayload - } - switch h.TransportProtocol() { - case header.TCPProtocolNumber: - nextParser = parseTCP - case header.UDPProtocolNumber: - nextParser = parseUDP - case header.ICMPv4ProtocolNumber: - nextParser = parseICMPv4 - default: - // Assume that the rest is a payload. - nextParser = parsePayload - } - return &ipv4, nextParser -} - -func (l *IPv4) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *IPv4) length() int { - if l.IHL == nil { - return header.IPv4MinimumSize - } - return int(*l.IHL) -} - -// merge implements Layer.merge. -func (l *IPv4) merge(other Layer) error { - return mergeLayer(l, other) -} - -// IPv6 can construct and match an IPv6 encapsulation. -type IPv6 struct { - LayerBase - TrafficClass *uint8 - FlowLabel *uint32 - PayloadLength *uint16 - NextHeader *uint8 - HopLimit *uint8 - SrcAddr *tcpip.Address - DstAddr *tcpip.Address -} - -func (l *IPv6) String() string { - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *IPv6) ToBytes() ([]byte, error) { - b := make([]byte, header.IPv6MinimumSize) - h := header.IPv6(b) - fields := &header.IPv6Fields{ - HopLimit: 64, - } - if l.TrafficClass != nil { - fields.TrafficClass = *l.TrafficClass - } - if l.FlowLabel != nil { - fields.FlowLabel = *l.FlowLabel - } - if l.PayloadLength != nil { - fields.PayloadLength = *l.PayloadLength - } else { - for current := l.next(); current != nil; current = current.next() { - fields.PayloadLength += uint16(current.length()) - } - } - if l.NextHeader != nil { - fields.TransportProtocol = tcpip.TransportProtocolNumber(*l.NextHeader) - } else { - nh, err := nextHeaderByLayer(l.next()) - if err != nil { - return nil, err - } - fields.TransportProtocol = tcpip.TransportProtocolNumber(nh) - } - if l.HopLimit != nil { - fields.HopLimit = *l.HopLimit - } - if l.SrcAddr != nil { - fields.SrcAddr = *l.SrcAddr - } - if l.DstAddr != nil { - fields.DstAddr = *l.DstAddr - } - h.Encode(fields) - return h, nil -} - -// nextIPv6PayloadParser finds the corresponding parser for nextHeader. -func nextIPv6PayloadParser(nextHeader uint8) layerParser { - switch tcpip.TransportProtocolNumber(nextHeader) { - case header.TCPProtocolNumber: - return parseTCP - case header.UDPProtocolNumber: - return parseUDP - case header.ICMPv6ProtocolNumber: - return parseICMPv6 - } - switch header.IPv6ExtensionHeaderIdentifier(nextHeader) { - case header.IPv6HopByHopOptionsExtHdrIdentifier: - return parseIPv6HopByHopOptionsExtHdr - case header.IPv6DestinationOptionsExtHdrIdentifier: - return parseIPv6DestinationOptionsExtHdr - case header.IPv6FragmentExtHdrIdentifier: - return parseIPv6FragmentExtHdr - } - return parsePayload -} - -// parseIPv6 parses the bytes assuming that they start with an ipv6 header and -// continues parsing further encapsulations. -func parseIPv6(b []byte) (Layer, layerParser) { - h := header.IPv6(b) - tos, flowLabel := h.TOS() - ipv6 := IPv6{ - TrafficClass: &tos, - FlowLabel: &flowLabel, - PayloadLength: Uint16(h.PayloadLength()), - NextHeader: Uint8(h.NextHeader()), - HopLimit: Uint8(h.HopLimit()), - SrcAddr: Address(h.SourceAddress()), - DstAddr: Address(h.DestinationAddress()), - } - nextParser := nextIPv6PayloadParser(h.NextHeader()) - return &ipv6, nextParser -} - -func (l *IPv6) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *IPv6) length() int { - return header.IPv6MinimumSize -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *IPv6) merge(other Layer) error { - return mergeLayer(l, other) -} - -// IPv6HopByHopOptionsExtHdr can construct and match an IPv6HopByHopOptions -// Extension Header. -type IPv6HopByHopOptionsExtHdr struct { - LayerBase - NextHeader *header.IPv6ExtensionHeaderIdentifier - Options []byte -} - -// IPv6DestinationOptionsExtHdr can construct and match an IPv6DestinationOptions -// Extension Header. -type IPv6DestinationOptionsExtHdr struct { - LayerBase - NextHeader *header.IPv6ExtensionHeaderIdentifier - Options []byte -} - -// IPv6FragmentExtHdr can construct and match an IPv6 Fragment Extension Header. -type IPv6FragmentExtHdr struct { - LayerBase - NextHeader *header.IPv6ExtensionHeaderIdentifier - FragmentOffset *uint16 - MoreFragments *bool - Identification *uint32 -} - -// nextHeaderByLayer finds the correct next header protocol value for layer l. -func nextHeaderByLayer(l Layer) (uint8, error) { - if l == nil { - return uint8(header.IPv6NoNextHeaderIdentifier), nil - } - switch l.(type) { - case *TCP: - return uint8(header.TCPProtocolNumber), nil - case *UDP: - return uint8(header.UDPProtocolNumber), nil - case *ICMPv6: - return uint8(header.ICMPv6ProtocolNumber), nil - case *Payload: - return uint8(header.IPv6NoNextHeaderIdentifier), nil - case *IPv6HopByHopOptionsExtHdr: - return uint8(header.IPv6HopByHopOptionsExtHdrIdentifier), nil - case *IPv6DestinationOptionsExtHdr: - return uint8(header.IPv6DestinationOptionsExtHdrIdentifier), nil - case *IPv6FragmentExtHdr: - return uint8(header.IPv6FragmentExtHdrIdentifier), nil - default: - // TODO(b/161005083): Support more protocols as needed. - return 0, fmt.Errorf("failed to deduce the IPv6 header's next protocol: %T", l) - } -} - -// ipv6OptionsExtHdrToBytes serializes an options extension header into bytes. -func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier, nextLayer Layer, options []byte) ([]byte, error) { - length := len(options) + 2 - if length%8 != 0 { - return nil, fmt.Errorf("IPv6 extension headers must be a multiple of 8 octets long, but the length given: %d, options: %s", length, hex.Dump(options)) - } - bytes := make([]byte, length) - if nextHeader != nil { - bytes[0] = byte(*nextHeader) - } else { - nh, err := nextHeaderByLayer(nextLayer) - if err != nil { - return nil, err - } - bytes[0] = nh - } - // ExtHdrLen field is the length of the extension header - // in 8-octet unit, ignoring the first 8 octets. - // https://tools.ietf.org/html/rfc2460#section-4.3 - // https://tools.ietf.org/html/rfc2460#section-4.6 - bytes[1] = uint8((length - 8) / 8) - copy(bytes[2:], options) - return bytes, nil -} - -// IPv6ExtHdrIdent is a helper routine that allocates a new -// header.IPv6ExtensionHeaderIdentifier value to store v and returns a pointer -// to it. -func IPv6ExtHdrIdent(id header.IPv6ExtensionHeaderIdentifier) *header.IPv6ExtensionHeaderIdentifier { - return &id -} - -// ToBytes implements Layer.ToBytes. -func (l *IPv6HopByHopOptionsExtHdr) ToBytes() ([]byte, error) { - return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options) -} - -// ToBytes implements Layer.ToBytes. -func (l *IPv6DestinationOptionsExtHdr) ToBytes() ([]byte, error) { - return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options) -} - -// ToBytes implements Layer.ToBytes. -func (l *IPv6FragmentExtHdr) ToBytes() ([]byte, error) { - var offset, mflag uint16 - var ident uint32 - bytes := make([]byte, header.IPv6FragmentExtHdrLength) - if l.NextHeader != nil { - bytes[0] = byte(*l.NextHeader) - } else { - nh, err := nextHeaderByLayer(l.next()) - if err != nil { - return nil, err - } - bytes[0] = nh - } - bytes[1] = 0 // reserved - if l.MoreFragments != nil && *l.MoreFragments { - mflag = 1 - } - if l.FragmentOffset != nil { - offset = *l.FragmentOffset - } - if l.Identification != nil { - ident = *l.Identification - } - offsetAndMflag := offset<<3 | mflag - binary.BigEndian.PutUint16(bytes[2:], offsetAndMflag) - binary.BigEndian.PutUint32(bytes[4:], ident) - - return bytes, nil -} - -// parseIPv6ExtHdr parses an IPv6 extension header and returns the NextHeader -// field, the rest of the payload and a parser function for the corresponding -// next extension header. -func parseIPv6ExtHdr(b []byte) (header.IPv6ExtensionHeaderIdentifier, []byte, layerParser) { - nextHeader := b[0] - // For HopByHop and Destination options extension headers, - // This field is the length of the extension header in - // 8-octet units, not including the first 8 octets. - // https://tools.ietf.org/html/rfc2460#section-4.3 - // https://tools.ietf.org/html/rfc2460#section-4.6 - length := b[1]*8 + 8 - data := b[2:length] - nextParser := nextIPv6PayloadParser(nextHeader) - return header.IPv6ExtensionHeaderIdentifier(nextHeader), data, nextParser -} - -// parseIPv6HopByHopOptionsExtHdr parses the bytes assuming that they start -// with an IPv6 HopByHop Options Extension Header. -func parseIPv6HopByHopOptionsExtHdr(b []byte) (Layer, layerParser) { - nextHeader, options, nextParser := parseIPv6ExtHdr(b) - return &IPv6HopByHopOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser -} - -// parseIPv6DestinationOptionsExtHdr parses the bytes assuming that they start -// with an IPv6 Destination Options Extension Header. -func parseIPv6DestinationOptionsExtHdr(b []byte) (Layer, layerParser) { - nextHeader, options, nextParser := parseIPv6ExtHdr(b) - return &IPv6DestinationOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser -} - -// Bool is a helper routine that allocates a new -// bool value to store v and returns a pointer to it. -func Bool(v bool) *bool { - return &v -} - -// parseIPv6FragmentExtHdr parses the bytes assuming that they start -// with an IPv6 Fragment Extension Header. -func parseIPv6FragmentExtHdr(b []byte) (Layer, layerParser) { - nextHeader := b[0] - var extHdr header.IPv6FragmentExtHdr - copy(extHdr[:], b[2:]) - fragLayer := IPv6FragmentExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(nextHeader)), - FragmentOffset: Uint16(extHdr.FragmentOffset()), - MoreFragments: Bool(extHdr.More()), - Identification: Uint32(extHdr.ID()), - } - // If it is a fragment, we can't interpret it. - if extHdr.FragmentOffset() != 0 || extHdr.More() { - return &fragLayer, parsePayload - } - return &fragLayer, nextIPv6PayloadParser(nextHeader) -} - -func (l *IPv6HopByHopOptionsExtHdr) length() int { - return len(l.Options) + 2 -} - -func (l *IPv6HopByHopOptionsExtHdr) match(other Layer) bool { - return equalLayer(l, other) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *IPv6HopByHopOptionsExtHdr) merge(other Layer) error { - return mergeLayer(l, other) -} - -func (l *IPv6HopByHopOptionsExtHdr) String() string { - return stringLayer(l) -} - -func (l *IPv6DestinationOptionsExtHdr) length() int { - return len(l.Options) + 2 -} - -func (l *IPv6DestinationOptionsExtHdr) match(other Layer) bool { - return equalLayer(l, other) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *IPv6DestinationOptionsExtHdr) merge(other Layer) error { - return mergeLayer(l, other) -} - -func (l *IPv6DestinationOptionsExtHdr) String() string { - return stringLayer(l) -} - -func (*IPv6FragmentExtHdr) length() int { - return header.IPv6FragmentExtHdrLength -} - -func (l *IPv6FragmentExtHdr) match(other Layer) bool { - return equalLayer(l, other) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *IPv6FragmentExtHdr) merge(other Layer) error { - return mergeLayer(l, other) -} - -func (l *IPv6FragmentExtHdr) String() string { - return stringLayer(l) -} - -// ICMPv6 can construct and match an ICMPv6 encapsulation. -type ICMPv6 struct { - LayerBase - Type *header.ICMPv6Type - Code *header.ICMPv6Code - Checksum *uint16 - Payload []byte -} - -func (l *ICMPv6) String() string { - // TODO(eyalsoha): Do something smarter here when *l.Type is ParameterProblem? - // We could parse the contents of the Payload as if it were an IPv6 packet. - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *ICMPv6) ToBytes() ([]byte, error) { - b := make([]byte, header.ICMPv6HeaderSize+len(l.Payload)) - h := header.ICMPv6(b) - if l.Type != nil { - h.SetType(*l.Type) - } - if l.Code != nil { - h.SetCode(*l.Code) - } - if n := copy(h.MessageBody(), l.Payload); n != len(l.Payload) { - panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload))) - } - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - } else { - // It is possible that the ICMPv6 header does not follow the IPv6 header - // immediately, there could be one or more extension headers in between. - // We need to search forward to find the IPv6 header. - for prev := l.Prev(); prev != nil; prev = prev.Prev() { - if ipv6, ok := prev.(*IPv6); ok { - payload, err := payload(l) - if err != nil { - return nil, err - } - h.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: h, - Src: *ipv6.SrcAddr, - Dst: *ipv6.DstAddr, - PayloadCsum: header.ChecksumVV(payload, 0 /* initial */), - PayloadLen: payload.Size(), - })) - break - } - } - } - return h, nil -} - -// ICMPv6Type is a helper routine that allocates a new ICMPv6Type value to store -// v and returns a pointer to it. -func ICMPv6Type(v header.ICMPv6Type) *header.ICMPv6Type { - return &v -} - -// ICMPv6Code is a helper routine that allocates a new ICMPv6Type value to store -// v and returns a pointer to it. -func ICMPv6Code(v header.ICMPv6Code) *header.ICMPv6Code { - return &v -} - -// Byte is a helper routine that allocates a new byte value to store -// v and returns a pointer to it. -func Byte(v byte) *byte { - return &v -} - -// parseICMPv6 parses the bytes assuming that they start with an ICMPv6 header. -func parseICMPv6(b []byte) (Layer, layerParser) { - h := header.ICMPv6(b) - icmpv6 := ICMPv6{ - Type: ICMPv6Type(h.Type()), - Code: ICMPv6Code(h.Code()), - Checksum: Uint16(h.Checksum()), - Payload: h.MessageBody(), - } - return &icmpv6, nil -} - -func (l *ICMPv6) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *ICMPv6) length() int { - return header.ICMPv6HeaderSize + len(l.Payload) -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *ICMPv6) merge(other Layer) error { - return mergeLayer(l, other) -} - -// ICMPv4 can construct and match an ICMPv4 encapsulation. -type ICMPv4 struct { - LayerBase - Type *header.ICMPv4Type - Code *header.ICMPv4Code - Checksum *uint16 - Ident *uint16 // Only in Echo Request/Reply. - Sequence *uint16 // Only in Echo Request/Reply. - Pointer *uint8 // Only in Parameter Problem. - Payload []byte -} - -func (l *ICMPv4) String() string { - return stringLayer(l) -} - -// ICMPv4Type is a helper routine that allocates a new header.ICMPv4Type value -// to store t and returns a pointer to it. -func ICMPv4Type(t header.ICMPv4Type) *header.ICMPv4Type { - return &t -} - -// ICMPv4Code is a helper routine that allocates a new header.ICMPv4Code value -// to store t and returns a pointer to it. -func ICMPv4Code(t header.ICMPv4Code) *header.ICMPv4Code { - return &t -} - -// ToBytes implements Layer.ToBytes. -func (l *ICMPv4) ToBytes() ([]byte, error) { - b := make([]byte, header.ICMPv4MinimumSize+len(l.Payload)) - h := header.ICMPv4(b) - if l.Type != nil { - h.SetType(*l.Type) - } - if l.Code != nil { - h.SetCode(*l.Code) - } - if copied := copy(h.Payload(), l.Payload); copied != len(l.Payload) { - panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", len(h.Payload()), len(l.Payload))) - } - typ := h.Type() - switch typ { - case header.ICMPv4EchoReply, header.ICMPv4Echo: - if l.Ident != nil { - h.SetIdent(*l.Ident) - } - if l.Sequence != nil { - h.SetSequence(*l.Sequence) - } - case header.ICMPv4ParamProblem: - if l.Pointer != nil { - h.SetPointer(*l.Pointer) - } - } - - // The checksum must be handled last because the ICMPv4 header fields are - // included in the computation. - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - } else { - // Compute the checksum based on the ICMPv4.Payload and also the subsequent - // layers. - payload, err := payload(l) - if err != nil { - return nil, err - } - var vv buffer.VectorisedView - vv.AppendView(buffer.View(l.Payload)) - vv.Append(payload) - h.SetChecksum(header.ICMPv4Checksum(h, header.ChecksumVV(vv, 0 /* initial */))) - } - - return h, nil -} - -// parseICMPv4 parses the bytes as an ICMPv4 header, returning a Layer and a -// parser for the encapsulated payload. -func parseICMPv4(b []byte) (Layer, layerParser) { - h := header.ICMPv4(b) - - msgType := h.Type() - icmpv4 := ICMPv4{ - Type: ICMPv4Type(msgType), - Code: ICMPv4Code(h.Code()), - Checksum: Uint16(h.Checksum()), - Payload: h.Payload(), - } - switch msgType { - case header.ICMPv4EchoReply, header.ICMPv4Echo: - icmpv4.Ident = Uint16(h.Ident()) - icmpv4.Sequence = Uint16(h.Sequence()) - case header.ICMPv4ParamProblem: - icmpv4.Pointer = Uint8(h.Pointer()) - } - return &icmpv4, nil -} - -func (l *ICMPv4) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *ICMPv4) length() int { - return header.ICMPv4MinimumSize -} - -// merge overrides the values in l with the values from other but only in fields -// where the value is not nil. -func (l *ICMPv4) merge(other Layer) error { - return mergeLayer(l, other) -} - -// TCP can construct and match a TCP encapsulation. -type TCP struct { - LayerBase - SrcPort *uint16 - DstPort *uint16 - SeqNum *uint32 - AckNum *uint32 - DataOffset *uint8 - Flags *header.TCPFlags - WindowSize *uint16 - Checksum *uint16 - UrgentPointer *uint16 - Options []byte -} - -func (l *TCP) String() string { - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *TCP) ToBytes() ([]byte, error) { - b := make([]byte, l.length()) - h := header.TCP(b) - if l.SrcPort != nil { - h.SetSourcePort(*l.SrcPort) - } - if l.DstPort != nil { - h.SetDestinationPort(*l.DstPort) - } - if l.SeqNum != nil { - h.SetSequenceNumber(*l.SeqNum) - } - if l.AckNum != nil { - h.SetAckNumber(*l.AckNum) - } - if l.DataOffset != nil { - h.SetDataOffset(*l.DataOffset) - } else { - h.SetDataOffset(uint8(l.length())) - } - if l.Flags != nil { - h.SetFlags(uint8(*l.Flags)) - } - if l.WindowSize != nil { - h.SetWindowSize(*l.WindowSize) - } else { - h.SetWindowSize(32768) - } - if l.UrgentPointer != nil { - h.SetUrgentPoiner(*l.UrgentPointer) - } - copy(b[header.TCPMinimumSize:], l.Options) - header.AddTCPOptionPadding(b[header.TCPMinimumSize:], len(l.Options)) - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - return h, nil - } - if err := setTCPChecksum(&h, l); err != nil { - return nil, err - } - return h, nil -} - -// totalLength returns the length of the provided layer and all following -// layers. -func totalLength(l Layer) int { - var totalLength int - for ; l != nil; l = l.next() { - totalLength += l.length() - } - return totalLength -} - -// payload returns a buffer.VectorisedView of l's payload. -func payload(l Layer) (buffer.VectorisedView, error) { - var payloadBytes buffer.VectorisedView - for current := l.next(); current != nil; current = current.next() { - payload, err := current.ToBytes() - if err != nil { - return buffer.VectorisedView{}, fmt.Errorf("can't get bytes for next header: %s", payload) - } - payloadBytes.AppendView(payload) - } - return payloadBytes, nil -} - -// layerChecksum calculates the checksum of the Layer header, including the -// peusdeochecksum of the layer before it and all the bytes after it. -func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) { - totalLength := uint16(totalLength(l)) - var xsum uint16 - switch p := l.Prev().(type) { - case *IPv4: - xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength) - case *IPv6: - xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength) - default: - // TODO(b/161246171): Support more protocols. - return 0, fmt.Errorf("checksum for protocol %d is not supported when previous layer is %T", protoNumber, p) - } - payloadBytes, err := payload(l) - if err != nil { - return 0, err - } - xsum = header.ChecksumVV(payloadBytes, xsum) - return xsum, nil -} - -// setTCPChecksum calculates the checksum of the TCP header and sets it in h. -func setTCPChecksum(h *header.TCP, tcp *TCP) error { - h.SetChecksum(0) - xsum, err := layerChecksum(tcp, header.TCPProtocolNumber) - if err != nil { - return err - } - h.SetChecksum(^h.CalculateChecksum(xsum)) - return nil -} - -// Uint32 is a helper routine that allocates a new -// uint32 value to store v and returns a pointer to it. -func Uint32(v uint32) *uint32 { - return &v -} - -// parseTCP parses the bytes assuming that they start with a tcp header and -// continues parsing further encapsulations. -func parseTCP(b []byte) (Layer, layerParser) { - h := header.TCP(b) - tcp := TCP{ - SrcPort: Uint16(h.SourcePort()), - DstPort: Uint16(h.DestinationPort()), - SeqNum: Uint32(h.SequenceNumber()), - AckNum: Uint32(h.AckNumber()), - DataOffset: Uint8(h.DataOffset()), - Flags: TCPFlags(h.Flags()), - WindowSize: Uint16(h.WindowSize()), - Checksum: Uint16(h.Checksum()), - UrgentPointer: Uint16(h.UrgentPointer()), - Options: b[header.TCPMinimumSize:h.DataOffset()], - } - return &tcp, parsePayload -} - -func (l *TCP) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *TCP) length() int { - if l.DataOffset == nil { - // TCP header including the options must end on a 32-bit - // boundary; the user could potentially give us a slice - // whose length is not a multiple of 4 bytes, so we have - // to do the alignment here. - optlen := (len(l.Options) + 3) & ^3 - return header.TCPMinimumSize + optlen - } - return int(*l.DataOffset) -} - -// merge implements Layer.merge. -func (l *TCP) merge(other Layer) error { - return mergeLayer(l, other) -} - -// UDP can construct and match a UDP encapsulation. -type UDP struct { - LayerBase - SrcPort *uint16 - DstPort *uint16 - Length *uint16 - Checksum *uint16 -} - -func (l *UDP) String() string { - return stringLayer(l) -} - -// ToBytes implements Layer.ToBytes. -func (l *UDP) ToBytes() ([]byte, error) { - b := make([]byte, header.UDPMinimumSize) - h := header.UDP(b) - if l.SrcPort != nil { - h.SetSourcePort(*l.SrcPort) - } - if l.DstPort != nil { - h.SetDestinationPort(*l.DstPort) - } - if l.Length != nil { - h.SetLength(*l.Length) - } else { - h.SetLength(uint16(totalLength(l))) - } - if l.Checksum != nil { - h.SetChecksum(*l.Checksum) - return h, nil - } - if err := setUDPChecksum(&h, l); err != nil { - return nil, err - } - return h, nil -} - -// setUDPChecksum calculates the checksum of the UDP header and sets it in h. -func setUDPChecksum(h *header.UDP, udp *UDP) error { - h.SetChecksum(0) - xsum, err := layerChecksum(udp, header.UDPProtocolNumber) - if err != nil { - return err - } - h.SetChecksum(^h.CalculateChecksum(xsum)) - return nil -} - -// parseUDP parses the bytes assuming that they start with a udp header and -// returns the parsed layer and the next parser to use. -func parseUDP(b []byte) (Layer, layerParser) { - h := header.UDP(b) - udp := UDP{ - SrcPort: Uint16(h.SourcePort()), - DstPort: Uint16(h.DestinationPort()), - Length: Uint16(h.Length()), - Checksum: Uint16(h.Checksum()), - } - return &udp, parsePayload -} - -func (l *UDP) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *UDP) length() int { - return header.UDPMinimumSize -} - -// merge implements Layer.merge. -func (l *UDP) merge(other Layer) error { - return mergeLayer(l, other) -} - -// Payload has bytes beyond OSI layer 4. -type Payload struct { - LayerBase - Bytes []byte -} - -func (l *Payload) String() string { - return stringLayer(l) -} - -// parsePayload parses the bytes assuming that they start with a payload and -// continue to the end. There can be no further encapsulations. -func parsePayload(b []byte) (Layer, layerParser) { - payload := Payload{ - Bytes: b, - } - return &payload, nil -} - -// ToBytes implements Layer.ToBytes. -func (l *Payload) ToBytes() ([]byte, error) { - return l.Bytes, nil -} - -// Length returns payload byte length. -func (l *Payload) Length() int { - return l.length() -} - -func (l *Payload) match(other Layer) bool { - return equalLayer(l, other) -} - -func (l *Payload) length() int { - return len(l.Bytes) -} - -// merge implements Layer.merge. -func (l *Payload) merge(other Layer) error { - return mergeLayer(l, other) -} - -// Layers is an array of Layer and supports similar functions to Layer. -type Layers []Layer - -// linkLayers sets the linked-list ponters in ls. -func (ls *Layers) linkLayers() { - for i, l := range *ls { - if i > 0 { - l.setPrev((*ls)[i-1]) - } else { - l.setPrev(nil) - } - if i+1 < len(*ls) { - l.setNext((*ls)[i+1]) - } else { - l.setNext(nil) - } - } -} - -// ToBytes converts the Layers into bytes. It creates a linked list of the Layer -// structs and then concatentates the output of ToBytes on each Layer. -func (ls *Layers) ToBytes() ([]byte, error) { - ls.linkLayers() - outBytes := []byte{} - for _, l := range *ls { - layerBytes, err := l.ToBytes() - if err != nil { - return nil, err - } - outBytes = append(outBytes, layerBytes...) - } - return outBytes, nil -} - -func (ls *Layers) match(other Layers) bool { - if len(*ls) > len(other) { - return false - } - for i, l := range *ls { - if !equalLayer(l, other[i]) { - return false - } - } - return true -} - -// layerDiff stores the diffs for each field along with the label for the Layer. -// If rows is nil, that means that there was no diff. -type layerDiff struct { - label string - rows []layerDiffRow -} - -// layerDiffRow stores the fields and corresponding values for two got and want -// layers. If the value was nil then the string stored is the empty string. -type layerDiffRow struct { - field, got, want string -} - -// diffLayer extracts all differing fields between two layers. -func diffLayer(got, want Layer) []layerDiffRow { - vGot := reflect.ValueOf(got).Elem() - vWant := reflect.ValueOf(want).Elem() - if vGot.Type() != vWant.Type() { - return nil - } - t := vGot.Type() - var result []layerDiffRow - for i := 0; i < t.NumField(); i++ { - t := t.Field(i) - if t.Anonymous { - // Ignore the LayerBase in the Layer struct. - continue - } - vGot := vGot.Field(i) - vWant := vWant.Field(i) - gotString := "" - if !vGot.IsNil() { - gotString = fmt.Sprint(reflect.Indirect(vGot)) - } - wantString := "" - if !vWant.IsNil() { - wantString = fmt.Sprint(reflect.Indirect(vWant)) - } - result = append(result, layerDiffRow{t.Name, gotString, wantString}) - } - return result -} - -// layerType returns a concise string describing the type of the Layer, like -// "TCP", or "IPv6". -func layerType(l Layer) string { - return reflect.TypeOf(l).Elem().Name() -} - -// diff compares Layers and returns a representation of the difference. Each -// Layer in the Layers is pairwise compared. If an element in either is nil, it -// is considered a match with the other Layer. If two Layers have differing -// types, they don't match regardless of the contents. If two Layers have the -// same type then the fields in the Layer are pairwise compared. Fields that are -// nil always match. Two non-nil fields only match if they point to equal -// values. diff returns an empty string if and only if *ls and other match. -func (ls *Layers) diff(other Layers) string { - var allDiffs []layerDiff - // Check the cases where one list is longer than the other, where one or both - // elements are nil, where the sides have different types, and where the sides - // have the same type. - for i := 0; i < len(*ls) || i < len(other); i++ { - if i >= len(*ls) { - // Matching ls against other where other is longer than ls. missing - // matches everything so we just include a label without any rows. Having - // no rows is a sign that there was no diff. - allDiffs = append(allDiffs, layerDiff{ - label: "missing matches " + layerType(other[i]), - }) - continue - } - - if i >= len(other) { - // Matching ls against other where ls is longer than other. missing - // matches everything so we just include a label without any rows. Having - // no rows is a sign that there was no diff. - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]) + " matches missing", - }) - continue - } - - if (*ls)[i] == nil && other[i] == nil { - // Matching ls against other where both elements are nil. nil matches - // everything so we just include a label without any rows. Having no rows - // is a sign that there was no diff. - allDiffs = append(allDiffs, layerDiff{ - label: "nil matches nil", - }) - continue - } - - if (*ls)[i] == nil { - // Matching ls against other where the element in ls is nil. nil matches - // everything so we just include a label without any rows. Having no rows - // is a sign that there was no diff. - allDiffs = append(allDiffs, layerDiff{ - label: "nil matches " + layerType(other[i]), - }) - continue - } - - if other[i] == nil { - // Matching ls against other where the element in other is nil. nil - // matches everything so we just include a label without any rows. Having - // no rows is a sign that there was no diff. - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]) + " matches nil", - }) - continue - } - - if reflect.TypeOf((*ls)[i]) == reflect.TypeOf(other[i]) { - // Matching ls against other where both elements have the same type. Match - // each field pairwise and only report a diff if there is a mismatch, - // which is only when both sides are non-nil and have differring values. - diff := diffLayer((*ls)[i], other[i]) - var layerDiffRows []layerDiffRow - for _, d := range diff { - if d.got == "" || d.want == "" || d.got == d.want { - continue - } - layerDiffRows = append(layerDiffRows, layerDiffRow{ - d.field, - d.got, - d.want, - }) - } - if len(layerDiffRows) > 0 { - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]), - rows: layerDiffRows, - }) - } else { - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]) + " matches " + layerType(other[i]), - // Having no rows is a sign that there was no diff. - }) - } - continue - } - // Neither side is nil and the types are different, so we'll display one - // side then the other. - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]) + " doesn't match " + layerType(other[i]), - }) - diff := diffLayer((*ls)[i], (*ls)[i]) - layerDiffRows := []layerDiffRow{} - for _, d := range diff { - if len(d.got) == 0 { - continue - } - layerDiffRows = append(layerDiffRows, layerDiffRow{ - d.field, - d.got, - "", - }) - } - allDiffs = append(allDiffs, layerDiff{ - label: layerType((*ls)[i]), - rows: layerDiffRows, - }) - - layerDiffRows = []layerDiffRow{} - diff = diffLayer(other[i], other[i]) - for _, d := range diff { - if len(d.want) == 0 { - continue - } - layerDiffRows = append(layerDiffRows, layerDiffRow{ - d.field, - "", - d.want, - }) - } - allDiffs = append(allDiffs, layerDiff{ - label: layerType(other[i]), - rows: layerDiffRows, - }) - } - - output := "" - // These are for output formatting. - maxLabelLen, maxFieldLen, maxGotLen, maxWantLen := 0, 0, 0, 0 - foundOne := false - for _, l := range allDiffs { - if len(l.label) > maxLabelLen && len(l.rows) > 0 { - maxLabelLen = len(l.label) - } - if l.rows != nil { - foundOne = true - } - for _, r := range l.rows { - if len(r.field) > maxFieldLen { - maxFieldLen = len(r.field) - } - if l := len(fmt.Sprint(r.got)); l > maxGotLen { - maxGotLen = l - } - if l := len(fmt.Sprint(r.want)); l > maxWantLen { - maxWantLen = l - } - } - } - if !foundOne { - return "" - } - for _, l := range allDiffs { - if len(l.rows) == 0 { - output += "(" + l.label + ")\n" - continue - } - for i, r := range l.rows { - var label string - if i == 0 { - label = l.label + ":" - } - output += fmt.Sprintf( - "%*s %*s %*v %*v\n", - maxLabelLen+1, label, - maxFieldLen+1, r.field+":", - maxGotLen, r.got, - maxWantLen, r.want, - ) - } - } - return output -} - -// merge merges the other Layers into ls. If the other Layers is longer, those -// additional Layer structs are added to ls. The errors from merging are -// collected and returned. -func (ls *Layers) merge(other Layers) error { - var errs error - for i, o := range other { - if i < len(*ls) { - errs = multierr.Combine(errs, (*ls)[i].merge(o)) - } else { - *ls = append(*ls, o) - } - } - return errs -} diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go deleted file mode 100644 index 614a5de1e..000000000 --- a/test/packetimpact/testbench/layers_test.go +++ /dev/null @@ -1,728 +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 testbench - -import ( - "bytes" - "net" - "testing" - - "github.com/mohae/deepcopy" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -func TestLayerMatch(t *testing.T) { - var nilPayload *Payload - noPayload := &Payload{} - emptyPayload := &Payload{Bytes: []byte{}} - fullPayload := &Payload{Bytes: []byte{1, 2, 3}} - emptyTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: emptyPayload}} - fullTCP := &TCP{SrcPort: Uint16(1234), LayerBase: LayerBase{nextLayer: fullPayload}} - for _, tt := range []struct { - a, b Layer - want bool - }{ - {nilPayload, nilPayload, true}, - {nilPayload, noPayload, true}, - {nilPayload, emptyPayload, true}, - {nilPayload, fullPayload, true}, - {noPayload, noPayload, true}, - {noPayload, emptyPayload, true}, - {noPayload, fullPayload, true}, - {emptyPayload, emptyPayload, true}, - {emptyPayload, fullPayload, false}, - {fullPayload, fullPayload, true}, - {emptyTCP, fullTCP, true}, - } { - if got := tt.a.match(tt.b); got != tt.want { - t.Errorf("%s.match(%s) = %t, want %t", tt.a, tt.b, got, tt.want) - } - if got := tt.b.match(tt.a); got != tt.want { - t.Errorf("%s.match(%s) = %t, want %t", tt.b, tt.a, got, tt.want) - } - } -} - -func TestLayerMergeMismatch(t *testing.T) { - tcp := &TCP{} - otherTCP := &TCP{} - ipv4 := &IPv4{} - ether := &Ether{} - for _, tt := range []struct { - a, b Layer - success bool - }{ - {tcp, tcp, true}, - {tcp, otherTCP, true}, - {tcp, ipv4, false}, - {tcp, ether, false}, - {tcp, nil, true}, - - {otherTCP, otherTCP, true}, - {otherTCP, ipv4, false}, - {otherTCP, ether, false}, - {otherTCP, nil, true}, - - {ipv4, ipv4, true}, - {ipv4, ether, false}, - {ipv4, nil, true}, - - {ether, ether, true}, - {ether, nil, true}, - } { - if err := tt.a.merge(tt.b); (err == nil) != tt.success { - t.Errorf("%s.merge(%s) got %s, wanted the opposite", tt.a, tt.b, err) - } - if tt.b != nil { - if err := tt.b.merge(tt.a); (err == nil) != tt.success { - t.Errorf("%s.merge(%s) got %s, wanted the opposite", tt.b, tt.a, err) - } - } - } -} - -func TestLayerMerge(t *testing.T) { - zero := Uint32(0) - one := Uint32(1) - two := Uint32(2) - empty := []byte{} - foo := []byte("foo") - bar := []byte("bar") - for _, tt := range []struct { - a, b Layer - want Layer - }{ - {&TCP{AckNum: nil}, &TCP{AckNum: nil}, &TCP{AckNum: nil}}, - {&TCP{AckNum: nil}, &TCP{AckNum: zero}, &TCP{AckNum: zero}}, - {&TCP{AckNum: nil}, &TCP{AckNum: one}, &TCP{AckNum: one}}, - {&TCP{AckNum: nil}, &TCP{AckNum: two}, &TCP{AckNum: two}}, - {&TCP{AckNum: nil}, nil, &TCP{AckNum: nil}}, - - {&TCP{AckNum: zero}, &TCP{AckNum: nil}, &TCP{AckNum: zero}}, - {&TCP{AckNum: zero}, &TCP{AckNum: zero}, &TCP{AckNum: zero}}, - {&TCP{AckNum: zero}, &TCP{AckNum: one}, &TCP{AckNum: one}}, - {&TCP{AckNum: zero}, &TCP{AckNum: two}, &TCP{AckNum: two}}, - {&TCP{AckNum: zero}, nil, &TCP{AckNum: zero}}, - - {&TCP{AckNum: one}, &TCP{AckNum: nil}, &TCP{AckNum: one}}, - {&TCP{AckNum: one}, &TCP{AckNum: zero}, &TCP{AckNum: zero}}, - {&TCP{AckNum: one}, &TCP{AckNum: one}, &TCP{AckNum: one}}, - {&TCP{AckNum: one}, &TCP{AckNum: two}, &TCP{AckNum: two}}, - {&TCP{AckNum: one}, nil, &TCP{AckNum: one}}, - - {&TCP{AckNum: two}, &TCP{AckNum: nil}, &TCP{AckNum: two}}, - {&TCP{AckNum: two}, &TCP{AckNum: zero}, &TCP{AckNum: zero}}, - {&TCP{AckNum: two}, &TCP{AckNum: one}, &TCP{AckNum: one}}, - {&TCP{AckNum: two}, &TCP{AckNum: two}, &TCP{AckNum: two}}, - {&TCP{AckNum: two}, nil, &TCP{AckNum: two}}, - - {&Payload{Bytes: nil}, &Payload{Bytes: nil}, &Payload{Bytes: nil}}, - {&Payload{Bytes: nil}, &Payload{Bytes: empty}, &Payload{Bytes: empty}}, - {&Payload{Bytes: nil}, &Payload{Bytes: foo}, &Payload{Bytes: foo}}, - {&Payload{Bytes: nil}, &Payload{Bytes: bar}, &Payload{Bytes: bar}}, - {&Payload{Bytes: nil}, nil, &Payload{Bytes: nil}}, - - {&Payload{Bytes: empty}, &Payload{Bytes: nil}, &Payload{Bytes: empty}}, - {&Payload{Bytes: empty}, &Payload{Bytes: empty}, &Payload{Bytes: empty}}, - {&Payload{Bytes: empty}, &Payload{Bytes: foo}, &Payload{Bytes: foo}}, - {&Payload{Bytes: empty}, &Payload{Bytes: bar}, &Payload{Bytes: bar}}, - {&Payload{Bytes: empty}, nil, &Payload{Bytes: empty}}, - - {&Payload{Bytes: foo}, &Payload{Bytes: nil}, &Payload{Bytes: foo}}, - {&Payload{Bytes: foo}, &Payload{Bytes: empty}, &Payload{Bytes: empty}}, - {&Payload{Bytes: foo}, &Payload{Bytes: foo}, &Payload{Bytes: foo}}, - {&Payload{Bytes: foo}, &Payload{Bytes: bar}, &Payload{Bytes: bar}}, - {&Payload{Bytes: foo}, nil, &Payload{Bytes: foo}}, - - {&Payload{Bytes: bar}, &Payload{Bytes: nil}, &Payload{Bytes: bar}}, - {&Payload{Bytes: bar}, &Payload{Bytes: empty}, &Payload{Bytes: empty}}, - {&Payload{Bytes: bar}, &Payload{Bytes: foo}, &Payload{Bytes: foo}}, - {&Payload{Bytes: bar}, &Payload{Bytes: bar}, &Payload{Bytes: bar}}, - {&Payload{Bytes: bar}, nil, &Payload{Bytes: bar}}, - } { - a := deepcopy.Copy(tt.a).(Layer) - if err := a.merge(tt.b); err != nil { - t.Errorf("%s.merge(%s) = %s, wanted nil", tt.a, tt.b, err) - continue - } - if a.String() != tt.want.String() { - t.Errorf("%s.merge(%s) merge result got %s, want %s", tt.a, tt.b, a, tt.want) - } - } -} - -func TestLayerStringFormat(t *testing.T) { - for _, tt := range []struct { - name string - l Layer - want string - }{ - { - name: "TCP", - l: &TCP{ - SrcPort: Uint16(34785), - DstPort: Uint16(47767), - SeqNum: Uint32(3452155723), - AckNum: Uint32(2596996163), - DataOffset: Uint8(5), - Flags: TCPFlags(header.TCPFlagRst | header.TCPFlagAck), - WindowSize: Uint16(64240), - Checksum: Uint16(0x2e2b), - }, - want: "&testbench.TCP{" + - "SrcPort:34785 " + - "DstPort:47767 " + - "SeqNum:3452155723 " + - "AckNum:2596996163 " + - "DataOffset:5 " + - "Flags: R A " + - "WindowSize:64240 " + - "Checksum:11819" + - "}", - }, - { - name: "UDP", - l: &UDP{ - SrcPort: Uint16(34785), - DstPort: Uint16(47767), - Length: Uint16(12), - }, - want: "&testbench.UDP{" + - "SrcPort:34785 " + - "DstPort:47767 " + - "Length:12" + - "}", - }, - { - name: "IPv4", - l: &IPv4{ - IHL: Uint8(5), - TOS: Uint8(0), - TotalLength: Uint16(44), - ID: Uint16(0), - Flags: Uint8(2), - FragmentOffset: Uint16(0), - TTL: Uint8(64), - Protocol: Uint8(6), - Checksum: Uint16(0x2e2b), - SrcAddr: Address(tcpip.Address([]byte{197, 34, 63, 10})), - DstAddr: Address(tcpip.Address([]byte{197, 34, 63, 20})), - }, - want: "&testbench.IPv4{" + - "IHL:5 " + - "TOS:0 " + - "TotalLength:44 " + - "ID:0 " + - "Flags:2 " + - "FragmentOffset:0 " + - "TTL:64 " + - "Protocol:6 " + - "Checksum:11819 " + - "SrcAddr:197.34.63.10 " + - "DstAddr:197.34.63.20" + - "}", - }, - { - name: "Ether", - l: &Ether{ - SrcAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x0a})), - DstAddr: LinkAddress(tcpip.LinkAddress([]byte{0x02, 0x42, 0xc5, 0x22, 0x3f, 0x14})), - Type: NetworkProtocolNumber(4), - }, - want: "&testbench.Ether{" + - "SrcAddr:02:42:c5:22:3f:0a " + - "DstAddr:02:42:c5:22:3f:14 " + - "Type:4" + - "}", - }, - { - name: "Payload", - l: &Payload{ - Bytes: []byte("Hooray for packetimpact."), - }, - want: "&testbench.Payload{Bytes:\n" + - "00000000 48 6f 6f 72 61 79 20 66 6f 72 20 70 61 63 6b 65 |Hooray for packe|\n" + - "00000010 74 69 6d 70 61 63 74 2e |timpact.|\n" + - "}", - }, - } { - t.Run(tt.name, func(t *testing.T) { - if got := tt.l.String(); got != tt.want { - t.Errorf("%s.String() = %s, want: %s", tt.name, got, tt.want) - } - }) - } -} - -func TestConnectionMatch(t *testing.T) { - conn := Connection{ - layerStates: []layerState{ðerState{}}, - } - protoNum0 := tcpip.NetworkProtocolNumber(0) - protoNum1 := tcpip.NetworkProtocolNumber(1) - for _, tt := range []struct { - description string - override, received Layers - wantMatch bool - }{ - { - description: "shorter override", - override: []Layer{&Ether{}}, - received: []Layer{&Ether{}, &Payload{Bytes: []byte("hello")}}, - wantMatch: true, - }, - { - description: "longer override", - override: []Layer{&Ether{}, &Payload{Bytes: []byte("hello")}}, - received: []Layer{&Ether{}}, - wantMatch: false, - }, - { - description: "ether layer mismatch", - override: []Layer{&Ether{Type: &protoNum0}}, - received: []Layer{&Ether{Type: &protoNum1}}, - wantMatch: false, - }, - { - description: "both nil", - override: nil, - received: nil, - wantMatch: false, - }, - { - description: "nil override", - override: nil, - received: []Layer{&Ether{}}, - wantMatch: true, - }, - } { - t.Run(tt.description, func(t *testing.T) { - if gotMatch := conn.match(tt.override, tt.received); gotMatch != tt.wantMatch { - t.Fatalf("conn.match(%s, %s) = %t, want %t", tt.override, tt.received, gotMatch, tt.wantMatch) - } - }) - } -} - -func TestLayersDiff(t *testing.T) { - for _, tt := range []struct { - x, y Layers - want string - }{ - { - Layers{&Ether{Type: NetworkProtocolNumber(12)}, &TCP{DataOffset: Uint8(5), SeqNum: Uint32(5)}}, - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - "Ether: Type: 12 13\n" + - " TCP: SeqNum: 5 6\n" + - " DataOffset: 5 7\n", - }, - { - Layers{&Ether{Type: NetworkProtocolNumber(12)}, &UDP{SrcPort: Uint16(123)}}, - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - "Ether: Type: 12 13\n" + - "(UDP doesn't match TCP)\n" + - " UDP: SrcPort: 123 \n" + - " TCP: SeqNum: 6\n" + - " DataOffset: 7\n", - }, - { - Layers{&UDP{SrcPort: Uint16(123)}}, - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - "(UDP doesn't match Ether)\n" + - " UDP: SrcPort: 123 \n" + - "Ether: Type: 13\n" + - "(missing matches TCP)\n", - }, - { - Layers{nil, &UDP{SrcPort: Uint16(123)}}, - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - "(nil matches Ether)\n" + - "(UDP doesn't match TCP)\n" + - "UDP: SrcPort: 123 \n" + - "TCP: SeqNum: 6\n" + - " DataOffset: 7\n", - }, - { - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &IPv4{IHL: Uint8(4)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - Layers{&Ether{Type: NetworkProtocolNumber(13)}, &IPv4{IHL: Uint8(6)}, &TCP{DataOffset: Uint8(7), SeqNum: Uint32(6)}}, - "(Ether matches Ether)\n" + - "IPv4: IHL: 4 6\n" + - "(TCP matches TCP)\n", - }, - { - Layers{&Payload{Bytes: []byte("foo")}}, - Layers{&Payload{Bytes: []byte("bar")}}, - "Payload: Bytes: [102 111 111] [98 97 114]\n", - }, - { - Layers{&Payload{Bytes: []byte("")}}, - Layers{&Payload{}}, - "", - }, - { - Layers{&Payload{Bytes: []byte("")}}, - Layers{&Payload{Bytes: []byte("")}}, - "", - }, - { - Layers{&UDP{}}, - Layers{&TCP{}}, - "(UDP doesn't match TCP)\n" + - "(UDP)\n" + - "(TCP)\n", - }, - } { - if got := tt.x.diff(tt.y); got != tt.want { - t.Errorf("%s.diff(%s) = %q, want %q", tt.x, tt.y, got, tt.want) - } - if tt.x.match(tt.y) != (tt.x.diff(tt.y) == "") { - t.Errorf("match and diff of %s and %s disagree", tt.x, tt.y) - } - if tt.y.match(tt.x) != (tt.y.diff(tt.x) == "") { - t.Errorf("match and diff of %s and %s disagree", tt.y, tt.x) - } - } -} - -func TestTCPOptions(t *testing.T) { - for _, tt := range []struct { - description string - wantBytes []byte - wantLayers Layers - }{ - { - description: "without payload", - wantBytes: []byte{ - // IPv4 Header - 0x45, 0x00, 0x00, 0x2c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, - 0xf9, 0x77, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01, - // TCP Header - 0x30, 0x39, 0xd4, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x60, 0x02, 0x20, 0x00, 0xf5, 0x1c, 0x00, 0x00, - // WindowScale Option - 0x03, 0x03, 0x02, - // NOP Option - 0x00, - }, - wantLayers: []Layer{ - &IPv4{ - IHL: Uint8(20), - TOS: Uint8(0), - TotalLength: Uint16(44), - ID: Uint16(1), - Flags: Uint8(0), - FragmentOffset: Uint16(0), - TTL: Uint8(64), - Protocol: Uint8(uint8(header.TCPProtocolNumber)), - Checksum: Uint16(0xf977), - SrcAddr: Address(tcpip.Address(net.ParseIP("192.168.0.2").To4())), - DstAddr: Address(tcpip.Address(net.ParseIP("192.168.0.1").To4())), - }, - &TCP{ - SrcPort: Uint16(12345), - DstPort: Uint16(54321), - SeqNum: Uint32(0), - AckNum: Uint32(0), - Flags: TCPFlags(header.TCPFlagSyn), - WindowSize: Uint16(8192), - Checksum: Uint16(0xf51c), - UrgentPointer: Uint16(0), - Options: []byte{3, 3, 2, 0}, - }, - &Payload{Bytes: nil}, - }, - }, - { - description: "with payload", - wantBytes: []byte{ - // IPv4 header - 0x45, 0x00, 0x00, 0x37, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, - 0xf9, 0x6c, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01, - // TCP header - 0x30, 0x39, 0xd4, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x60, 0x02, 0x20, 0x00, 0xe5, 0x21, 0x00, 0x00, - // WindowScale Option - 0x03, 0x03, 0x02, - // NOP Option - 0x00, - // Payload: "Sample Data" - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61, - }, - wantLayers: []Layer{ - &IPv4{ - IHL: Uint8(20), - TOS: Uint8(0), - TotalLength: Uint16(55), - ID: Uint16(1), - Flags: Uint8(0), - FragmentOffset: Uint16(0), - TTL: Uint8(64), - Protocol: Uint8(uint8(header.TCPProtocolNumber)), - Checksum: Uint16(0xf96c), - SrcAddr: Address(tcpip.Address(net.ParseIP("192.168.0.2").To4())), - DstAddr: Address(tcpip.Address(net.ParseIP("192.168.0.1").To4())), - }, - &TCP{ - SrcPort: Uint16(12345), - DstPort: Uint16(54321), - SeqNum: Uint32(0), - AckNum: Uint32(0), - Flags: TCPFlags(header.TCPFlagSyn), - WindowSize: Uint16(8192), - Checksum: Uint16(0xe521), - UrgentPointer: Uint16(0), - Options: []byte{3, 3, 2, 0}, - }, - &Payload{Bytes: []byte("Sample Data")}, - }, - }, - } { - t.Run(tt.description, func(t *testing.T) { - layers := parse(parseIPv4, tt.wantBytes) - if !layers.match(tt.wantLayers) { - t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers)) - } - gotBytes, err := layers.ToBytes() - if err != nil { - t.Fatalf("ToBytes() failed on %s: %s", &layers, err) - } - if !bytes.Equal(tt.wantBytes, gotBytes) { - t.Fatalf("mismatching bytes, gotBytes: %x, wantBytes: %x", gotBytes, tt.wantBytes) - } - }) - } -} - -func TestIPv6ExtHdrOptions(t *testing.T) { - for _, tt := range []struct { - description string - wantBytes []byte - wantLayers Layers - }{ - { - description: "IPv6/HopByHop", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // HopByHop Options - 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6HopByHopOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &Payload{ - Bytes: nil, - }, - }, - }, - { - description: "IPv6/HopByHop/Payload", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // HopByHop Options - 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - // Sample Data - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6HopByHopOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &Payload{ - Bytes: []byte("Sample Data"), - }, - }, - }, - { - description: "IPv6/HopByHop/Destination/ICMPv6", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // HopByHop Options - 0x3c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - // Destination Options - 0x3a, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - // ICMPv6 Param Problem - 0x04, 0x00, 0x5f, 0x98, 0x00, 0x00, 0x00, 0x06, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6HopByHopOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6DestinationOptionsExtHdrIdentifier), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &IPv6DestinationOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &ICMPv6{ - Type: ICMPv6Type(header.ICMPv6ParamProblem), - Code: ICMPv6Code(header.ICMPv6ErroneousHeader), - Checksum: Uint16(0x5f98), - Payload: []byte{0x00, 0x00, 0x00, 0x06}, - }, - }, - }, - { - description: "IPv6/HopByHop/Fragment", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // HopByHop Options - 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - // Fragment ExtHdr - 0x3b, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x2a, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6HopByHopOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &IPv6FragmentExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier), - FragmentOffset: Uint16(100), - MoreFragments: Bool(false), - Identification: Uint32(42), - }, - &Payload{ - Bytes: nil, - }, - }, - }, - { - description: "IPv6/DestOpt/Fragment/Payload", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x3c, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // Destination Options - 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00, - // Fragment ExtHdr - 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a, - // Sample Data - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6DestinationOptionsExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier), - Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00}, - }, - &IPv6FragmentExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier), - FragmentOffset: Uint16(100), - MoreFragments: Bool(true), - Identification: Uint32(42), - }, - &Payload{ - Bytes: []byte("Sample Data"), - }, - }, - }, - { - description: "IPv6/Fragment/Payload", - wantBytes: []byte{ - // IPv6 Header - 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2c, 0x40, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, - // Fragment ExtHdr - 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a, - // Sample Data - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61, - }, - wantLayers: []Layer{ - &IPv6{ - SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))), - DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))), - }, - &IPv6FragmentExtHdr{ - NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier), - FragmentOffset: Uint16(100), - MoreFragments: Bool(true), - Identification: Uint32(42), - }, - &Payload{ - Bytes: []byte("Sample Data"), - }, - }, - }, - } { - t.Run(tt.description, func(t *testing.T) { - layers := parse(parseIPv6, tt.wantBytes) - if !layers.match(tt.wantLayers) { - t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers)) - } - // Make sure we can generate correct next header values and checksums - for _, layer := range layers { - switch layer := layer.(type) { - case *IPv6HopByHopOptionsExtHdr: - layer.NextHeader = nil - case *IPv6DestinationOptionsExtHdr: - layer.NextHeader = nil - case *IPv6FragmentExtHdr: - layer.NextHeader = nil - case *ICMPv6: - layer.Checksum = nil - } - } - gotBytes, err := layers.ToBytes() - if err != nil { - t.Fatalf("ToBytes() failed on %s: %s", &layers, err) - } - if !bytes.Equal(tt.wantBytes, gotBytes) { - t.Fatalf("mismatching bytes, gotBytes: %x, wantBytes: %x", gotBytes, tt.wantBytes) - } - }) - } -} diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go deleted file mode 100644 index 1ac96626a..000000000 --- a/test/packetimpact/testbench/rawsockets.go +++ /dev/null @@ -1,207 +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 testbench - -import ( - "encoding/binary" - "fmt" - "math" - "net" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/usermem" -) - -// Sniffer can sniff raw packets on the wire. -type Sniffer struct { - fd int -} - -func htons(x uint16) uint16 { - buf := [2]byte{} - binary.BigEndian.PutUint16(buf[:], x) - return usermem.ByteOrder.Uint16(buf[:]) -} - -// NewSniffer creates a Sniffer connected to *device. -func (n *DUTTestNet) NewSniffer(t *testing.T) (Sniffer, error) { - t.Helper() - - ifInfo, err := net.InterfaceByName(n.LocalDevName) - if err != nil { - return Sniffer{}, err - } - - var haddr [8]byte - copy(haddr[:], ifInfo.HardwareAddr) - sa := unix.SockaddrLinklayer{ - Protocol: htons(unix.ETH_P_ALL), - Ifindex: ifInfo.Index, - } - snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL))) - if err != nil { - return Sniffer{}, err - } - if err := unix.Bind(snifferFd, &sa); err != nil { - return Sniffer{}, err - } - if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, 1); err != nil { - t.Fatalf("can't set sockopt SO_RCVBUFFORCE to 1: %s", err) - } - if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUF, 1e7); err != nil { - t.Fatalf("can't setsockopt SO_RCVBUF to 10M: %s", err) - } - return Sniffer{ - fd: snifferFd, - }, nil -} - -// maxReadSize should be large enough for the maximum frame size in bytes. If a -// packet too large for the buffer arrives, the test will get a fatal error. -const maxReadSize int = 65536 - -// Recv tries to read one frame until the timeout is up. If the timeout given -// is 0, then no read attempt will be made. -func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte { - t.Helper() - - deadline := time.Now().Add(timeout) - for { - timeout = deadline.Sub(time.Now()) - if timeout <= 0 { - return nil - } - whole, frac := math.Modf(timeout.Seconds()) - tv := unix.Timeval{ - Sec: int64(whole), - Usec: int64(frac * float64(time.Second/time.Microsecond)), - } - // The following should never happen, but having this guard here is better - // than blocking indefinitely in the future. - if tv.Sec == 0 && tv.Usec == 0 { - t.Fatal("setting SO_RCVTIMEO to 0 means blocking indefinitely") - } - if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil { - t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err) - } - - buf := make([]byte, maxReadSize) - nread, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC) - if err == unix.EINTR || err == unix.EAGAIN { - // There was a timeout. - continue - } - if err != nil { - t.Fatalf("can't read: %s", err) - } - if nread > maxReadSize { - t.Fatalf("received a truncated frame of %d bytes, want at most %d bytes", nread, maxReadSize) - } - return buf[:nread] - } -} - -// Drain drains the Sniffer's socket receive buffer by receiving until there's -// nothing else to receive. -func (s *Sniffer) Drain(t *testing.T) { - t.Helper() - - flags, err := unix.FcntlInt(uintptr(s.fd), unix.F_GETFL, 0) - if err != nil { - t.Fatalf("failed to get sniffer socket fd flags: %s", err) - } - nonBlockingFlags := flags | unix.O_NONBLOCK - if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, nonBlockingFlags); err != nil { - t.Fatalf("failed to make sniffer socket non-blocking with flags %b: %s", nonBlockingFlags, err) - } - for { - buf := make([]byte, maxReadSize) - _, _, err := unix.Recvfrom(s.fd, buf, unix.MSG_TRUNC) - if err == unix.EINTR || err == unix.EAGAIN || err == unix.EWOULDBLOCK { - break - } - } - if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags); err != nil { - t.Fatalf("failed to restore sniffer socket fd flags to %b: %s", flags, err) - } -} - -// close the socket that Sniffer is using. -func (s *Sniffer) close() error { - if err := unix.Close(s.fd); err != nil { - return fmt.Errorf("can't close sniffer socket: %w", err) - } - s.fd = -1 - return nil -} - -// Injector can inject raw frames. -type Injector struct { - fd int -} - -// NewInjector creates a new injector on *device. -func (n *DUTTestNet) NewInjector(t *testing.T) (Injector, error) { - t.Helper() - - ifInfo, err := net.InterfaceByName(n.LocalDevName) - if err != nil { - return Injector{}, err - } - - var haddr [8]byte - copy(haddr[:], ifInfo.HardwareAddr) - sa := unix.SockaddrLinklayer{ - Protocol: htons(unix.ETH_P_IP), - Ifindex: ifInfo.Index, - Halen: uint8(len(ifInfo.HardwareAddr)), - Addr: haddr, - } - - injectFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL))) - if err != nil { - return Injector{}, err - } - if err := unix.Bind(injectFd, &sa); err != nil { - return Injector{}, err - } - return Injector{ - fd: injectFd, - }, nil -} - -// Send a raw frame. -func (i *Injector) Send(t *testing.T, b []byte) { - t.Helper() - - n, err := unix.Write(i.fd, b) - if err != nil { - t.Fatalf("can't write bytes of len %d: %s", len(b), err) - } - if n != len(b) { - t.Fatalf("got %d bytes written, want %d", n, len(b)) - } -} - -// close the underlying socket. -func (i *Injector) close() error { - if err := unix.Close(i.fd); err != nil { - return fmt.Errorf("can't close sniffer socket: %w", err) - } - i.fd = -1 - return nil -} diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go deleted file mode 100644 index a73c07e64..000000000 --- a/test/packetimpact/testbench/testbench.go +++ /dev/null @@ -1,157 +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 testbench has utilities to send and receive packets, and also command -// the DUT to run POSIX functions. It is the packetimpact test API. -package testbench - -import ( - "encoding/json" - "flag" - "fmt" - "math/rand" - "net" - "testing" - "time" -) - -var ( - // Native indicates that the test is being run natively. - Native = false - // RPCKeepalive is the gRPC keepalive. - RPCKeepalive = 10 * time.Second - // RPCTimeout is the gRPC timeout. - RPCTimeout = 100 * time.Millisecond - - // dutInfosJSON is the json string that describes information about all the - // duts available to use. - dutInfosJSON string - // dutInfo is the pool among which the testbench can choose a DUT to work - // with. - dutInfo chan *DUTInfo -) - -// DUTInfo has both network and uname information about the DUT. -type DUTInfo struct { - Uname *DUTUname - Net *DUTTestNet -} - -// DUTUname contains information about the DUT from uname. -type DUTUname struct { - Machine string - KernelName string - KernelRelease string - KernelVersion string - OperatingSystem string -} - -// DUTTestNet describes the test network setup on dut and how the testbench -// should connect with an existing DUT. -type DUTTestNet struct { - // LocalMAC is the local MAC address on the test network. - LocalMAC net.HardwareAddr - // RemoteMAC is the DUT's MAC address on the test network. - RemoteMAC net.HardwareAddr - // LocalIPv4 is the local IPv4 address on the test network. - LocalIPv4 net.IP - // RemoteIPv4 is the DUT's IPv4 address on the test network. - RemoteIPv4 net.IP - // IPv4PrefixLength is the network prefix length of the IPv4 test network. - IPv4PrefixLength int - // LocalIPv6 is the local IPv6 address on the test network. - LocalIPv6 net.IP - // RemoteIPv6 is the DUT's IPv6 address on the test network. - RemoteIPv6 net.IP - // LocalDevID is the ID of the local interface on the test network. - LocalDevID uint32 - // RemoteDevID is the ID of the remote interface on the test network. - RemoteDevID uint32 - // LocalDevName is the device that testbench uses to inject traffic. - LocalDevName string - // RemoteDevName is the device name on the DUT, individual tests can - // use the name to construct tests. - RemoteDevName string - - // The following two fields on actually on the control network instead - // of the test network, including them for convenience. - - // POSIXServerIP is the POSIX server's IP address on the control network. - POSIXServerIP net.IP - // POSIXServerPort is the UDP port the POSIX server is bound to on the - // control network. - POSIXServerPort uint16 -} - -// registerFlags defines flags and associates them with the package-level -// exported variables above. It should be called by tests in their init -// functions. -func registerFlags(fs *flag.FlagSet) { - fs.BoolVar(&Native, "native", Native, "whether the test is running natively") - fs.DurationVar(&RPCTimeout, "rpc_timeout", RPCTimeout, "gRPC timeout") - fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive") - fs.StringVar(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs") -} - -// Initialize initializes the testbench, it parse the flags and sets up the -// pool of test networks for testbench's later use. -func Initialize(fs *flag.FlagSet) { - registerFlags(fs) - flag.Parse() - if err := loadDUTInfos(); err != nil { - panic(err) - } -} - -// loadDUTInfos loads available DUT test infos from the json file, it -// must be called after flag.Parse(). -func loadDUTInfos() error { - var dutInfos []DUTInfo - if err := json.Unmarshal([]byte(dutInfosJSON), &dutInfos); err != nil { - return fmt.Errorf("failed to unmarshal JSON: %w", err) - } - if got, want := len(dutInfos), 1; got < want { - return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want) - } - // Using a buffered channel as semaphore - dutInfo = make(chan *DUTInfo, len(dutInfos)) - for i := range dutInfos { - dutInfos[i].Net.LocalIPv4 = dutInfos[i].Net.LocalIPv4.To4() - dutInfos[i].Net.RemoteIPv4 = dutInfos[i].Net.RemoteIPv4.To4() - dutInfo <- &dutInfos[i] - } - return nil -} - -// GenerateRandomPayload generates a random byte slice of the specified length, -// causing a fatal test failure if it is unable to do so. -func GenerateRandomPayload(t *testing.T, n int) []byte { - t.Helper() - buf := make([]byte, n) - if _, err := rand.Read(buf); err != nil { - t.Fatalf("rand.Read(buf) failed: %s", err) - } - return buf -} - -// getDUTInfo returns information about an available DUT from the pool. If no -// DUT is readily available, getDUTInfo blocks until one becomes available. -func getDUTInfo() *DUTInfo { - return <-dutInfo -} - -// release returns the DUTInfo back to the pool. -func (info *DUTInfo) release() { - dutInfo <- info -} diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD deleted file mode 100644 index d5cb0ae06..000000000 --- a/test/packetimpact/tests/BUILD +++ /dev/null @@ -1,425 +0,0 @@ -load("//test/packetimpact/runner:defs.bzl", "ALL_TESTS", "packetimpact_go_test", "packetimpact_testbench", "validate_all_tests") - -package( - default_visibility = ["//test/packetimpact:__subpackages__"], - licenses = ["notice"], -) - -packetimpact_testbench( - name = "fin_wait2_timeout", - srcs = ["fin_wait2_timeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv4_id_uniqueness", - srcs = ["ipv4_id_uniqueness_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_discard_mcast_source_addr", - srcs = ["udp_discard_mcast_source_addr_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_any_addr_recv_unicast", - srcs = ["udp_any_addr_recv_unicast_test.go"], - deps = [ - "//pkg/tcpip", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_icmp_error_propagation", - srcs = ["udp_icmp_error_propagation_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_window_shrink", - srcs = ["tcp_window_shrink_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe", - srcs = ["tcp_zero_window_probe_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe_retransmit", - srcs = ["tcp_zero_window_probe_retransmit_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_window_probe_usertimeout", - srcs = ["tcp_zero_window_probe_usertimeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_retransmits", - srcs = ["tcp_retransmits_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/binary", - "//pkg/tcpip/header", - "//pkg/usermem", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_outside_the_window", - srcs = ["tcp_outside_the_window_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_outside_the_window_closing", - srcs = ["tcp_outside_the_window_closing_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_noaccept_close_rst", - srcs = ["tcp_noaccept_close_rst_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_send_window_sizes_piggyback", - srcs = ["tcp_send_window_sizes_piggyback_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_unacc_seq_ack", - srcs = ["tcp_unacc_seq_ack_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_unacc_seq_ack_closing", - srcs = ["tcp_unacc_seq_ack_closing_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_paws_mechanism", - srcs = ["tcp_paws_mechanism_test.go"], - deps = [ - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_user_timeout", - srcs = ["tcp_user_timeout_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_queue_send_recv_in_syn_sent", - srcs = ["tcp_queue_send_recv_in_syn_sent_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_synsent_reset", - srcs = ["tcp_synsent_reset_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_synrcvd_reset", - srcs = ["tcp_synrcvd_reset_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_network_unreachable", - srcs = ["tcp_network_unreachable_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_cork_mss", - srcs = ["tcp_cork_mss_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_handshake_window_size", - srcs = ["tcp_handshake_window_size_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_timewait_reset", - srcs = ["tcp_timewait_reset_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "icmpv6_param_problem", - srcs = ["icmpv6_param_problem_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_unknown_options_action", - srcs = ["ipv6_unknown_options_action_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv4_fragment_reassembly", - srcs = ["ipv4_fragment_reassembly_test.go"], - deps = [ - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_fragment_reassembly", - srcs = ["ipv6_fragment_reassembly_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "ipv6_fragment_icmp_error", - srcs = ["ipv6_fragment_icmp_error_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/network/ipv6", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "udp_send_recv_dgram", - srcs = ["udp_send_recv_dgram_test.go"], - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_linger", - srcs = ["tcp_linger_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_rcv_buf_space", - srcs = ["tcp_rcv_buf_space_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_zero_receive_window", - srcs = ["tcp_zero_receive_window_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_rack", - srcs = ["tcp_rack_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/binary", - "//pkg/tcpip/header", - "//pkg/tcpip/seqnum", - "//pkg/usermem", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_info", - srcs = ["tcp_info_test.go"], - deps = [ - "//pkg/abi/linux", - "//pkg/binary", - "//pkg/tcpip/header", - "//pkg/usermem", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -packetimpact_testbench( - name = "tcp_fin_retransmission", - srcs = ["tcp_fin_retransmission_test.go"], - deps = [ - "//pkg/tcpip/header", - "//test/packetimpact/testbench", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -validate_all_tests() - -[packetimpact_go_test( - name = t.name, - expect_netstack_failure = hasattr(t, "expect_netstack_failure"), - num_duts = t.num_duts if hasattr(t, "num_duts") else 1, -) for t in ALL_TESTS] - -test_suite( - name = "all_tests", - tags = [ - "manual", - "packetimpact", - ], - tests = existing_rules(), -) diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go deleted file mode 100644 index cff8ca51d..000000000 --- a/test/packetimpact/tests/fin_wait2_timeout_test.go +++ /dev/null @@ -1,74 +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 fin_wait2_timeout_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestFinWait2Timeout(t *testing.T) { - for _, tt := range []struct { - description string - linger2 bool - }{ - {"WithLinger2", true}, - {"WithoutLinger2", false}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - - acceptFd, _ := dut.Accept(t, listenFd) - if tt.linger2 { - tv := unix.Timeval{Sec: 1, Usec: 0} - dut.SetSockOptTimeval(t, acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv) - } - dut.Close(t, acceptFd) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - time.Sleep(5 * time.Second) - conn.Drain(t) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if tt.linger2 { - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected a RST packet within a second but got none: %s", err) - } - } else { - if got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil { - t.Fatalf("expected no RST packets within ten seconds but got one: %s", got) - } - } - }) - } -} diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go deleted file mode 100644 index 1beccb6cf..000000000 --- a/test/packetimpact/tests/icmpv6_param_problem_test.go +++ /dev/null @@ -1,77 +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 icmpv6_param_problem_test - -import ( - "encoding/binary" - "flag" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestICMPv6ParamProblemTest sends a packet with a bad next header. The DUT -// should respond with an ICMPv6 Parameter Problem message. -func TestICMPv6ParamProblemTest(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - ipv6 := testbench.IPv6{ - // 254 is reserved and used for experimentation and testing. This should - // cause an error. - NextHeader: testbench.Uint8(254), - } - icmpv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Payload: []byte("hello world"), - } - - toSend := conn.CreateFrame(t, testbench.Layers{&ipv6}, &icmpv6) - conn.SendFrame(t, toSend) - - // Build the expected ICMPv6 payload, which includes an index to the - // problematic byte and also the problematic packet as described in - // https://tools.ietf.org/html/rfc4443#page-12 . - ipv6Sent := toSend[1:] - expectedPayload, err := ipv6Sent.ToBytes() - if err != nil { - t.Fatalf("can't convert %s to bytes: %s", ipv6Sent, err) - } - - // The problematic field is the NextHeader. - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, header.IPv6NextHeaderOffset) - expectedPayload = append(b, expectedPayload...) - expectedICMPv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem), - Payload: expectedPayload, - } - - paramProblem := testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &expectedICMPv6, - } - timeout := time.Second - if _, err := conn.ExpectFrame(t, paramProblem, timeout); err != nil { - t.Errorf("expected %s within %s but got none: %s", paramProblem, timeout, err) - } -} diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go deleted file mode 100644 index 707f0f1f5..000000000 --- a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go +++ /dev/null @@ -1,175 +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 ipv4_fragment_reassembly_test - -import ( - "flag" - "math/rand" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type fragmentInfo struct { - offset uint16 - size uint16 - more uint8 - id uint16 -} - -func TestIPv4FragmentReassembly(t *testing.T) { - icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber) - - tests := []struct { - description string - ipPayloadLen int - fragments []fragmentInfo - expectReply bool - }{ - { - description: "basic reassembly", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 5, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 5, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 5, more: 0}, - }, - expectReply: true, - }, - { - description: "out of order fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 2000, size: 1000, id: 6, more: 0}, - {offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, - }, - expectReply: true, - }, - { - description: "duplicated fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 7, more: 0}, - }, - expectReply: true, - }, - { - description: "fragment subset", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 8, more: 0}, - }, - expectReply: true, - }, - { - description: "fragment overlap", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, - {offset: 2000, size: 1000, id: 9, more: 0}, - }, - expectReply: false, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) - defer conn.Close(t) - - data := make([]byte, test.ipPayloadLen) - icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize]) - icmp.SetType(header.ICMPv4Echo) - icmp.SetCode(header.ICMPv4UnusedCode) - icmp.SetChecksum(0) - icmp.SetSequence(0) - icmp.SetIdent(0) - originalPayload := data[header.ICMPv4MinimumSize:] - if _, err := rand.Read(originalPayload); err != nil { - t.Fatalf("rand.Read: %s", err) - } - cksum := header.ICMPv4Checksum(icmp, header.Checksum(originalPayload, 0 /* initial */)) - icmp.SetChecksum(cksum) - - for _, fragment := range test.fragments { - conn.Send(t, - testbench.IPv4{ - Protocol: &icmpv4ProtoNum, - FragmentOffset: testbench.Uint16(fragment.offset), - Flags: testbench.Uint8(fragment.more), - ID: testbench.Uint16(fragment.id), - }, - &testbench.Payload{ - Bytes: data[fragment.offset:][:fragment.size], - }) - } - - var bytesReceived int - reassembledPayload := make([]byte, test.ipPayloadLen) - // We are sending a packet fragmented into smaller parts but the - // response may also be large enough to require fragmentation. - // Therefore we only look for payload for an IPv4 packet not ICMP. - for { - incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv4{}, - }, time.Second) - if err != nil { - // Either an unexpected frame was received, or none at all. - if test.expectReply && bytesReceived < test.ipPayloadLen { - t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) - } - break - } - if !test.expectReply { - t.Fatalf("unexpected reply received:\n%s", incomingFrame) - } - // We only asked for Ethernet and IPv4 so the rest should be payload. - ipPayload, err := incomingFrame[2 /* Payload */].ToBytes() - if err != nil { - t.Fatalf("failed to parse payload: incomingPacket[2].ToBytes() = (_, %s)", err) - } - offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset - if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { - t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) - } - bytesReceived += len(ipPayload) - } - - if test.expectReply { - if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" { - t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go deleted file mode 100644 index 2b69ceecb..000000000 --- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go +++ /dev/null @@ -1,121 +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 ipv4_id_uniqueness_test - -import ( - "context" - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) { - layers, err := conn.ExpectData(t, expect, expectPayload, time.Second) - if err != nil { - return 0, fmt.Errorf("failed to receive TCP segment: %s", err) - } - if len(layers) < 2 { - return 0, fmt.Errorf("got packet with layers: %v, expected to have at least 2 layers (link and network)", layers) - } - ipv4, ok := layers[1].(*testbench.IPv4) - if !ok { - return 0, fmt.Errorf("got network layer: %T, expected: *IPv4", layers[1]) - } - if *ipv4.Flags&header.IPv4FlagDontFragment != 0 { - return 0, fmt.Errorf("got IPv4 DF=1, expected DF=0") - } - return *ipv4.ID, nil -} - -// RFC 6864 section 4.2 states: "The IPv4 ID of non-atomic datagrams MUST NOT -// be reused when sending a copy of an earlier non-atomic datagram." -// -// This test creates a TCP connection, uses the IP_MTU_DISCOVER socket option -// to force the DF bit to be 0, and checks that a retransmitted segment has a -// different IPv4 Identification value than the original segment. -func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) { - for _, tc := range []struct { - name string - payload []byte - }{ - {"SmallPayload", []byte("sample data")}, - // 512 bytes is chosen because sending more than this in a single segment - // causes the retransmission to send less than the original amount. - {"512BytePayload", testbench.GenerateRandomPayload(t, 512)}, - } { - t.Run(tc.name, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - remoteFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, remoteFD) - - dut.SetSockOptInt(t, remoteFD, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - // TODO(b/129291778) The following socket option clears the DF bit on - // IP packets sent over the socket, and is currently not supported by - // gVisor. gVisor by default sends packets with DF=0 anyway, so the - // socket option being not supported does not affect the operation of - // this test. Once the socket option is supported, the following call - // can be changed to simply assert success. - ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT) - // Fuchsia will return ENOPROTOPT errno. - if ret == -1 && errno != unix.ENOPROTOOPT { - t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno) - } - - samplePayload := &testbench.Payload{Bytes: tc.payload} - - dut.Send(t, remoteFD, tc.payload, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("failed to receive TCP segment sent for RTT calculation: %s", err) - } - // Let the DUT estimate RTO with RTT from the DATA-ACK. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip sending this ACK. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Send(t, remoteFD, tc.payload, 0) - expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))} - originalID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload) - if err != nil { - t.Fatalf("failed to receive TCP segment: %s", err) - } - - retransmitID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload) - if err != nil { - t.Fatalf("failed to receive retransmitted TCP segment: %s", err) - } - if originalID == retransmitID { - t.Fatalf("unexpectedly got retransmitted TCP segment with same IPv4 ID field=%d", originalID) - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go deleted file mode 100644 index 4034a128e..000000000 --- a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go +++ /dev/null @@ -1,356 +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 ipv6_fragment_icmp_error_test - -import ( - "flag" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -const ( - data = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT" - fragmentID = 1 - reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.IPv6Conn, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) { - t.Helper() - - icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize)) - icmpv6Header.SetType(header.ICMPv6EchoRequest) - icmpv6Header.SetCode(header.ICMPv6UnusedCode) - icmpv6Header.SetIdent(0) - icmpv6Header.SetSequence(0) - cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmpv6Header, - Src: tcpip.Address(n.LocalIPv6), - Dst: tcpip.Address(n.RemoteIPv6), - PayloadCsum: header.Checksum(payload, 0 /* initial */), - PayloadLen: len(payload), - }) - icmpv6Header.SetChecksum(cksum) - icmpv6Bytes := append([]byte(icmpv6Header), payload...) - - icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - - firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.Payload{ - Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength], - }, - ) - firstIPv6 := firstFragment[1:] - firstIPv6Bytes, err := firstIPv6.ToBytes() - if err != nil { - t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err) - } - - secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(secondFragmentOffset), - MoreFragments: testbench.Bool(false), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.Payload{ - Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:], - }, - ) - secondIPv6 := secondFragment[1:] - secondIPv6Bytes, err := secondIPv6.ToBytes() - if err != nil { - t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err) - } - - return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes} -} - -func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) { - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - }{ - { - name: "reassemble two fragments", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1, 2}, - }, - { - name: "reassemble two fragments in reverse order", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{2, 1}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotEchoReply, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - }, - }, time.Second) - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err) - } - gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:] - wantPayload := test.payload - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestIPv6FragmentReassemblyTimeout(t *testing.T) { - type icmpFramePattern struct { - typ header.ICMPv6Type - code header.ICMPv6Code - } - - type icmpReassemblyTimeoutDetail struct { - payloadFragment int // 1: first fragment, 2: second fragnemt. - } - - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - replyFilter icmpFramePattern - expectErrorReply bool - expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail - }{ - { - name: "reassembly timeout (first fragment only)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: true, - expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ - payloadFragment: 1, - }, - }, - { - name: "reassembly timeout (second fragment only)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: false, - }, - { - name: "reassembly timeout (two fragments with a gap)", - firstPayloadLength: 8, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8, - sendFrameOrder: []int{1, 2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6TimeExceeded, - code: header.ICMPv6ReassemblyTimeout, - }, - expectErrorReply: true, - expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ - payloadFragment: 1, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(test.replyFilter.typ), - Code: testbench.ICMPv6Code(test.replyFilter.code), - }, - }, reassemblyTimeout) - if !test.expectErrorReply { - if err == nil { - t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code) - } - return - } - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) - } - gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] - wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1] - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} - -func TestIPv6FragmentParamProblem(t *testing.T) { - type icmpFramePattern struct { - typ header.ICMPv6Type - code header.ICMPv6Code - } - - type icmpParamProblemDetail struct { - pointer uint32 - payloadFragment int // 1: first fragment, 2: second fragnemt. - } - - tests := []struct { - name string - firstPayloadLength uint16 - payload []byte - secondFragmentOffset uint16 - sendFrameOrder []int - replyFilter icmpFramePattern - expectICMPParamProblem icmpParamProblemDetail - }{ - { - name: "payload size not a multiple of 8", - firstPayloadLength: 9, - payload: []byte(data)[:20], - secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, - sendFrameOrder: []int{1}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6ParamProblem, - code: header.ICMPv6ErroneousHeader, - }, - expectICMPParamProblem: icmpParamProblemDetail{ - pointer: 4, - payloadFragment: 1, - }, - }, - { - name: "payload length error", - firstPayloadLength: 16, - payload: []byte(data)[:33], - secondFragmentOffset: 65520 / 8, - sendFrameOrder: []int{1, 2}, - replyFilter: icmpFramePattern{ - typ: header.ICMPv6ParamProblem, - code: header.ICMPv6ErroneousHeader, - }, - expectICMPParamProblem: icmpParamProblemDetail{ - pointer: 42, - payloadFragment: 2, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) - - for _, i := range test.sendFrameOrder { - conn.SendFrame(t, fragments[i-1]) - } - - gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(test.replyFilter.typ), - Code: testbench.ICMPv6Code(test.replyFilter.code), - }, - }, time.Second) - if err != nil { - t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) - } - gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() - if err != nil { - t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) - } - gotPointer := header.ICMPv6(gotPayload).TypeSpecific() - wantPointer := test.expectICMPParamProblem.pointer - if gotPointer != wantPointer { - t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer) - } - icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] - wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1] - if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { - t.Fatalf("payload mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go deleted file mode 100644 index db6195dc9..000000000 --- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go +++ /dev/null @@ -1,182 +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 ipv6_fragment_reassembly_test - -import ( - "flag" - "math/rand" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type fragmentInfo struct { - offset uint16 - size uint16 - more bool - id uint32 -} - -func TestIPv6FragmentReassembly(t *testing.T) { - icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - - tests := []struct { - description string - ipPayloadLen int - fragments []fragmentInfo - expectReply bool - }{ - { - description: "basic reassembly", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 100, more: true}, - {offset: 1000, size: 1000, id: 100, more: true}, - {offset: 2000, size: 1000, id: 100, more: false}, - }, - expectReply: true, - }, - { - description: "out of order fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 101, more: true}, - {offset: 2000, size: 1000, id: 101, more: false}, - {offset: 1000, size: 1000, id: 101, more: true}, - }, - expectReply: true, - }, - { - description: "duplicated fragments", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 102, more: true}, - {offset: 1000, size: 1000, id: 102, more: true}, - {offset: 1000, size: 1000, id: 102, more: true}, - {offset: 2000, size: 1000, id: 102, more: false}, - }, - expectReply: true, - }, - { - description: "fragment subset", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 103, more: true}, - {offset: 1000, size: 1000, id: 103, more: true}, - {offset: 512, size: 256, id: 103, more: true}, - {offset: 2000, size: 1000, id: 103, more: false}, - }, - expectReply: true, - }, - { - description: "fragment overlap", - ipPayloadLen: 3000, - fragments: []fragmentInfo{ - {offset: 0, size: 1000, id: 104, more: true}, - {offset: 1512, size: 1000, id: 104, more: true}, - {offset: 1000, size: 1000, id: 104, more: true}, - {offset: 2000, size: 1000, id: 104, more: false}, - }, - expectReply: false, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - lIP := tcpip.Address(dut.Net.LocalIPv6) - rIP := tcpip.Address(dut.Net.RemoteIPv6) - - data := make([]byte, test.ipPayloadLen) - icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize]) - icmp.SetType(header.ICMPv6EchoRequest) - icmp.SetCode(header.ICMPv6UnusedCode) - icmp.SetChecksum(0) - originalPayload := data[header.ICMPv6HeaderSize:] - if _, err := rand.Read(originalPayload); err != nil { - t.Fatalf("rand.Read: %s", err) - } - - cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ - Header: icmp, - Src: lIP, - Dst: rIP, - PayloadCsum: header.Checksum(originalPayload, 0 /* initial */), - PayloadLen: len(originalPayload), - }) - icmp.SetChecksum(cksum) - - for _, fragment := range test.fragments { - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit), - MoreFragments: testbench.Bool(fragment.more), - Identification: testbench.Uint32(fragment.id), - }, - &testbench.Payload{ - Bytes: data[fragment.offset:][:fragment.size], - }) - } - - var bytesReceived int - reassembledPayload := make([]byte, test.ipPayloadLen) - for { - incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{}, - }, time.Second) - if err != nil { - // Either an unexpected frame was received, or none at all. - if test.expectReply && bytesReceived < test.ipPayloadLen { - t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) - } - break - } - if !test.expectReply { - t.Fatalf("unexpected reply received:\n%s", incomingFrame) - } - ipPayload, err := incomingFrame[3 /* Payload */].ToBytes() - if err != nil { - t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err) - } - offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset - offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit - if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { - t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) - } - bytesReceived += len(ipPayload) - } - - if test.expectReply { - if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" { - t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go deleted file mode 100644 index f999d13d2..000000000 --- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go +++ /dev/null @@ -1,185 +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 ipv6_unknown_options_action_test - -import ( - "encoding/binary" - "flag" - "net" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer { - return &testbench.IPv6HopByHopOptionsExtHdr{ - Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00}, - } -} - -func mkDestinationOptionsExtHdr(optType byte) testbench.Layer { - return &testbench.IPv6DestinationOptionsExtHdr{ - Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00}, - } -} - -func optionTypeFromAction(action header.IPv6OptionUnknownAction) byte { - return byte(action << 6) -} - -func TestIPv6UnknownOptionAction(t *testing.T) { - for _, tt := range []struct { - description string - mkExtHdr func(optType byte) testbench.Layer - action header.IPv6OptionUnknownAction - multicastDst bool - wantICMPv6 bool - }{ - { - description: "0b00/hbh", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionSkip, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b01/hbh", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscard, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b10/hbh/unicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b10/hbh/multicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: true, - wantICMPv6: true, - }, - { - description: "0b11/hbh/unicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b11/hbh/multicast", - mkExtHdr: mkHopByHopOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: true, - wantICMPv6: false, - }, - { - description: "0b00/destination", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionSkip, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b01/destination", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscard, - multicastDst: false, - wantICMPv6: false, - }, - { - description: "0b10/destination/unicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b10/destination/multicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMP, - multicastDst: true, - wantICMPv6: true, - }, - { - description: "0b11/destination/unicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: false, - wantICMPv6: true, - }, - { - description: "0b11/destination/multicast", - mkExtHdr: mkDestinationOptionsExtHdr, - action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, - multicastDst: true, - wantICMPv6: false, - }, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - outgoingOverride := testbench.Layers{} - if tt.multicastDst { - outgoingOverride = testbench.Layers{&testbench.IPv6{ - DstAddr: testbench.Address(tcpip.Address(net.ParseIP("ff02::1"))), - }} - } - - outgoing := conn.CreateFrame(t, outgoingOverride, tt.mkExtHdr(optionTypeFromAction(tt.action))) - conn.SendFrame(t, outgoing) - ipv6Sent := outgoing[1:] - invokingPacket, err := ipv6Sent.ToBytes() - if err != nil { - t.Fatalf("failed to serialize the outgoing packet: %s", err) - } - icmpv6Payload := make([]byte, 4) - // The pointer in the ICMPv6 parameter problem message should point to - // the option type of the unknown option. In our test case, it is the - // first option in the extension header whose option type is 2 bytes - // after the IPv6 header (after NextHeader and ExtHdrLen). - binary.BigEndian.PutUint32(icmpv6Payload, header.IPv6MinimumSize+2) - icmpv6Payload = append(icmpv6Payload, invokingPacket...) - gotICMPv6, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem), - Code: testbench.ICMPv6Code(header.ICMPv6UnknownOption), - Payload: icmpv6Payload, - }, - }, time.Second) - if tt.wantICMPv6 && err != nil { - t.Fatalf("expected ICMPv6 Parameter Problem but got none: %s", err) - } - if !tt.wantICMPv6 && gotICMPv6 != nil { - t.Fatalf("expected no ICMPv6 Parameter Problem but got one: %s", gotICMPv6) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go deleted file mode 100644 index 1db3c9883..000000000 --- a/test/packetimpact/tests/tcp_cork_mss_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_cork_mss_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPCorkMSS tests for segment coalesce and split as per MSS. -func TestTCPCorkMSS(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - const mss = uint32(header.TCPDefaultMSS) - options := make([]byte, header.TCPOptionMSSLength) - header.EncodeMSSOption(mss, options) - conn.ConnectWithOptions(t, options) - - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - dut.SetSockOptInt(t, acceptFD, unix.IPPROTO_TCP, unix.TCP_CORK, 1) - - // Let the dut application send 2 small segments to be held up and coalesced - // until the application sends a larger segment to fill up to > MSS. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - dut.Send(t, acceptFD, sampleData, 0) - - expectedData := sampleData - expectedData = append(expectedData, sampleData...) - largeData := make([]byte, mss+1) - expectedData = append(expectedData, largeData...) - dut.Send(t, acceptFD, largeData, 0) - - // Expect the segments to be coalesced and sent and capped to MSS. - expectedPayload := testbench.Payload{Bytes: expectedData[:mss]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the coalesced segment to be split and transmitted. - expectedPayload = testbench.Payload{Bytes: expectedData[mss:]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check for segments to *not* be held up because of TCP_CORK when - // the current send window is less than MSS. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))}) - dut.Send(t, acceptFD, sampleData, 0) - dut.Send(t, acceptFD, sampleData, 0) - expectedPayload = testbench.Payload{Bytes: append(sampleData, sampleData...)} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} diff --git a/test/packetimpact/tests/tcp_fin_retransmission_test.go b/test/packetimpact/tests/tcp_fin_retransmission_test.go deleted file mode 100644 index 500f7a783..000000000 --- a/test/packetimpact/tests/tcp_fin_retransmission_test.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 tcp_fin_retransmission_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPClosingFinRetransmission tests that TCP implementation should retransmit -// FIN segment in CLOSING state. -func TestTCPClosingFinRetransmission(t *testing.T) { - for _, tt := range []struct { - description string - flags header.TCPFlags - }{ - {"CLOSING", header.TCPFlagAck | header.TCPFlagFin}, - {"FIN_WAIT_1", header.TCPFlagAck}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip the next block of code. - sampleData := []byte("Sample Data") - if got, want := dut.Send(t, acceptFD, sampleData, 0), len(sampleData); int(got) != want { - t.Fatalf("got dut.Send(t, %d, %s, 0) = %d, want %d", acceptFD, sampleData, got, want) - } - if _, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected FINACK from DUT, but got none: %s", err) - } - - // Do not ack the FIN from DUT so that we can test for retransmission. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(tt.flags)}) - - if tt.flags&header.TCPFlagFin != 0 { - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - } - - if _, err := conn.Expect(t, testbench.TCP{ - SeqNum: seqNumForTheirFIN, - Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck), - }, time.Second); err != nil { - t.Errorf("expected retransmission of FIN from the DUT: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go deleted file mode 100644 index 668e0275c..000000000 --- a/test/packetimpact/tests/tcp_handshake_window_size_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_handshake_window_size_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPHandshakeWindowSize tests if the stack is honoring the window size -// communicated during handshake. -func TestTCPHandshakeWindowSize(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - // Start handshake with zero window size. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK: %s", err) - } - // Update the advertised window size to a non-zero value with the ACK that - // completes the handshake. - // - // Set the window size with MSB set and expect the dut to treat it as - // an unsigned value. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))}) - - acceptFd, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFd) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Since we advertised a zero window followed by a non-zero window, - // expect the dut to honor the recently advertised non-zero window - // and actually send out the data instead of probing for zero window. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go deleted file mode 100644 index 3fc2c7fe5..000000000 --- a/test/packetimpact/tests/tcp_info_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_info_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestTCPInfo(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - // Send and receive sample data. - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - dut.Send(t, acceptFD, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - info := linux.TCPInfo{} - infoBytes := dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) - if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want { - t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want) - } - binary.Unmarshal(infoBytes, usermem.ByteOrder, &info) - - rtt := time.Duration(info.RTT) * time.Microsecond - rttvar := time.Duration(info.RTTVar) * time.Microsecond - rto := time.Duration(info.RTO) * time.Microsecond - if rtt == 0 || rttvar == 0 || rto == 0 { - t.Errorf("expected rtt(%v), rttvar(%v) and rto(%v) to be greater than zero", rtt, rttvar, rto) - } - if info.ReordSeen != 0 { - t.Errorf("expected the connection to not have any reordering, got: %v want: 0", info.ReordSeen) - } - if info.SndCwnd == 0 { - t.Errorf("expected send congestion window to be greater than zero") - } - if info.CaState != linux.TCP_CA_Open { - t.Errorf("expected the connection to be in open state, got: %v want: %v", info.CaState, linux.TCP_CA_Open) - } - - if t.Failed() { - t.FailNow() - } - - // Check the congestion control state and send congestion window after - // retransmission timeout. - seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - dut.Send(t, acceptFD, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - // Expect retransmission of the packet within 1.5*RTO. - timeout := time.Duration(float64(info.RTO)*1.5) * time.Microsecond - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - info = linux.TCPInfo{} - infoBytes = dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) - if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want { - t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want) - } - binary.Unmarshal(infoBytes, usermem.ByteOrder, &info) - if info.CaState != linux.TCP_CA_Loss { - t.Errorf("expected the connection to be in loss recovery, got: %v want: %v", info.CaState, linux.TCP_CA_Loss) - } - if info.SndCwnd != 1 { - t.Errorf("expected send congestion window to be 1, got: %v %v", info.SndCwnd) - } -} diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go deleted file mode 100644 index 88942904d..000000000 --- a/test/packetimpact/tests/tcp_linger_test.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_linger_test - -import ( - "context" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) { - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - return acceptFD, listenFD, conn -} - -func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) { - conn.Close(t) - dut.Close(t, listenFD) -} - -// lingerDuration is the timeout value used with SO_LINGER socket option. -const lingerDuration = 3 * time.Second - -// TestTCPLingerZeroTimeout tests when SO_LINGER is set with zero timeout. DUT -// should send RST-ACK when socket is closed. -func TestTCPLingerZeroTimeout(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, 0, true) - dut.Close(t, acceptFD) - - // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected RST-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK -// when socket is closed. -func TestTCPLingerOff(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.Close(t, acceptFD) - - // If SO_LINGER is not set, DUT should send a FIN-ACK. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout. -// DUT should close the socket after timeout. -func TestTCPLingerNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"WithNonZeroLinger", true}, - {"WithoutLinger", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -// TestTCPLingerSendNonZeroTimeout tests when SO_LINGER is set with non-zero -// timeout and send a packet. DUT should close the socket after timeout. -func TestTCPLingerSendNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"WithSendNonZeroLinger", true}, - {"WithoutLinger", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - // Send data. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") - } - - samplePayload := &testbench.Payload{Bytes: sampleData} - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -// TestTCPLingerShutdownZeroTimeout tests SO_LINGER with shutdown() and zero -// timeout. DUT should send RST-ACK when socket is closed. -func TestTCPLingerShutdownZeroTimeout(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, 0, true) - dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) - dut.Close(t, acceptFD) - - // Shutdown will send FIN-ACK with read/write option. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - - // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected RST-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) -} - -// TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and -// non-zero timeout. DUT should close the socket after timeout. -func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { - for _, tt := range []struct { - description string - lingerOn bool - }{ - {"shutdownRDWR", true}, - {"shutdownRDWR", false}, - } { - t.Run(tt.description, func(t *testing.T) { - // Create a socket, listen, TCP connect, and accept. - dut := testbench.NewDUT(t) - acceptFD, listenFD, conn := createSocket(t, dut) - defer closeAll(t, dut, listenFD, conn) - - dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn) - - // Send data. - sampleData := []byte("Sample Data") - dut.Send(t, acceptFD, sampleData, 0) - - dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) - - // Increase timeout as Close will take longer time to - // return when SO_LINGER is set with non-zero timeout. - timeout := lingerDuration + 1*time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - start := time.Now() - dut.CloseWithErrno(ctx, t, acceptFD) - end := time.Now() - diff := end.Sub(start) - - if tt.lingerOn && diff < lingerDuration { - t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration) - } else if !tt.lingerOn && diff > 1*time.Second { - t.Errorf("expected close to return within a second, but returned later") - } - - samplePayload := &testbench.Payload{Bytes: sampleData} - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) - } - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - }) - } -} - -func TestTCPLingerNonEstablished(t *testing.T) { - dut := testbench.NewDUT(t) - newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) - dut.SetSockLingerOption(t, newFD, lingerDuration, true) - - // As the socket is in the initial state, Close() should not linger - // and return immediately. - start := time.Now() - dut.CloseWithErrno(context.Background(), t, newFD) - diff := time.Since(start) - - if diff > lingerDuration { - t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff) - } -} diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go deleted file mode 100644 index 5168450ad..000000000 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_synsent_reset_test - -import ( - "context" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when -// an ICMP destination unreachable message is sent in response to the inital -// SYN. -func TestTCPSynSentUnreachable(t *testing.T) { - // Create the DUT and connection. - dut := testbench.NewDUT(t) - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - port := uint16(9001) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) - defer conn.Close(t) - - // Bring the DUT to SYN-SENT state with a non-blocking connect. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() - sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], dut.Net.LocalIPv4) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { - t.Errorf("got connect() = %v, want EINPROGRESS", err) - } - - // Get the SYN. - tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second) - if err != nil { - t.Fatalf("expected SYN: %s", err) - } - - // Send a host unreachable message. - layers := conn.CreateFrame(t, nil) - layers = layers[:len(layers)-1] - const ipLayer = 1 - const tcpLayer = ipLayer + 1 - ip, ok := tcpLayers[ipLayer].(*testbench.IPv4) - if !ok { - t.Fatalf("expected %s to be IPv4", tcpLayers[ipLayer]) - } - tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP) - if !ok { - t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer]) - } - var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable), - } - - layers = append(layers, &icmpv4, ip, tcp) - conn.SendFrameStateless(t, layers) - - if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH { - t.Errorf("got connect() = %v, want EHOSTUNREACH", err) - } -} - -// TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when -// an ICMP destination unreachable message is sent in response to the inital -// SYN. -func TestTCPSynSentUnreachable6(t *testing.T) { - // Create the DUT and connection. - dut := testbench.NewDUT(t) - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6) - conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) - defer conn.Close(t) - - // Bring the DUT to SYN-SENT state with a non-blocking connect. - ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) - defer cancel() - sa := unix.SockaddrInet6{ - Port: int(conn.SrcPort()), - ZoneId: dut.Net.RemoteDevID, - } - copy(sa.Addr[:], dut.Net.LocalIPv6) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { - t.Errorf("got connect() = %v, want EINPROGRESS", err) - } - - // Get the SYN. - tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second) - if err != nil { - t.Fatalf("expected SYN: %s", err) - } - - // Send a host unreachable message. - layers := conn.CreateFrame(t, nil) - layers = layers[:len(layers)-1] - const ipLayer = 1 - const tcpLayer = ipLayer + 1 - ip, ok := tcpLayers[ipLayer].(*testbench.IPv6) - if !ok { - t.Fatalf("expected %s to be IPv6", tcpLayers[ipLayer]) - } - tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP) - if !ok { - t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer]) - } - var icmpv6 testbench.ICMPv6 = testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable), - Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable), - // Per RFC 4443 3.1, the payload contains 4 zeroed bytes. - Payload: []byte{0, 0, 0, 0}, - } - layers = append(layers, &icmpv6, ip, tcp) - conn.SendFrameStateless(t, layers) - - if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH { - t.Errorf("got connect() = %v, want EHOSTUNREACH", err) - } -} - -// getConnectError gets the errno generated by the on-going connect attempt on -// fd. fd must be non-blocking and there must be a connect call to fd which -// returned EINPROGRESS before. These conditions are guaranteed in this test. -func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error { - t.Helper() - // We previously got EINPROGRESS form the connect call. We can - // handle it as explained by connect(2): - // EINPROGRESS: - // The socket is nonblocking and the connection cannot be - // completed immediately. It is possible to select(2) or poll(2) - // for completion by selecting the socket for writing. After - // select(2) indicates writability, use getsockopt(2) to read - // the SO_ERROR option at level SOL_SOCKET to determine - // whether connect() completed successfully (SO_ERROR is - // zero) or unsuccessfully (SO_ERROR is one of the usual - // error codes listed here, explaining the reason for the - // failure). - dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second) - if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { - return unix.Errno(errno) - } - return nil -} diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go deleted file mode 100644 index 14eb7d93b..000000000 --- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_noaccept_close_rst_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestTcpNoAcceptCloseReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - conn.Connect(t) - defer conn.Close(t) - // We need to wait for POLLIN event on listenFd to know the connection is - // established. Otherwise there could be a race when we issue the Close - // command prior to the DUT receiving the last ack of the handshake and - // it will only respond RST instead of RST+ACK. - dut.PollOne(t, listenFd, unix.POLLIN, time.Second) - dut.Close(t, listenFd) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil { - t.Fatalf("expected a RST-ACK packet but got none: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_outside_the_window_closing_test.go b/test/packetimpact/tests/tcp_outside_the_window_closing_test.go deleted file mode 100644 index 1097746c7..000000000 --- a/test/packetimpact/tests/tcp_outside_the_window_closing_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_outside_the_window_closing_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestAckOTWSeqInClosing tests that the DUT should send an ACK with -// the right ACK number when receiving a packet with OTW Seq number -// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69 -func TestAckOTWSeqInClosing(t *testing.T) { - for seqNumOffset := seqnum.Size(0); seqNumOffset < 3; seqNumOffset++ { - for _, tt := range []struct { - description string - flags header.TCPFlags - payloads testbench.Layers - }{ - {"SYN", header.TCPFlagSyn, nil}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil}, - {"ACK", header.TCPFlagAck, nil}, - {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}}, - } { - t.Run(fmt.Sprintf("%s%d", tt.description, seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected FINACK from DUT, but got none: %s", err) - } - - // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - - windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + seqNumOffset - conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{ - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - AckNum: seqNumForTheirFIN, - Flags: testbench.TCPFlags(tt.flags), - }}, tt.payloads...)) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK but got none: %s", err) - } - }) - } - } -} diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go deleted file mode 100644 index 7cd7ff703..000000000 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_outside_the_window_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive -// that are inside or outside the TCP window. Packets that are outside the -// window should force an extra ACK, as described in RFC793 page 69: -// https://tools.ietf.org/html/rfc793#page-69 -func TestTCPOutsideTheWindow(t *testing.T) { - for _, tt := range []struct { - description string - tcpFlags header.TCPFlags - payload []testbench.Layer - seqNumOffset seqnum.Size - expectACK bool - }{ - {"SYN", header.TCPFlagSyn, nil, 0, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true}, - {"ACK", header.TCPFlagAck, nil, 0, false}, - {"FIN", header.TCPFlagFin, nil, 0, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 0, true}, - - {"SYN", header.TCPFlagSyn, nil, 1, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true}, - {"ACK", header.TCPFlagAck, nil, 1, true}, - {"FIN", header.TCPFlagFin, nil, 1, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 1, true}, - - {"SYN", header.TCPFlagSyn, nil, 2, true}, - {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true}, - {"ACK", header.TCPFlagAck, nil, 2, true}, - {"FIN", header.TCPFlagFin, nil, 2, false}, - {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 2, true}, - } { - t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + tt.seqNumOffset - conn.Drain(t) - // Ignore whatever incrementing that this out-of-order packet might cause - // to the AckNum. - localSeqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - conn.Send(t, testbench.TCP{ - Flags: testbench.TCPFlags(tt.tcpFlags), - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - }, tt.payload...) - timeout := time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) - if tt.expectACK && err != nil { - t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err) - } - // Data packets w/o SYN bits are always acked by Linux. Netstack ACK's data packets - // always right now. So only send a second segment and test for no ACK for packets - // with no data. - if tt.expectACK && tt.payload == nil { - // Sending another out-of-window segment immediately should not trigger - // an ACK if less than 500ms(default rate limit for out-of-window ACKs) - // has passed since the last ACK was sent. - t.Logf("sending another segment") - conn.Send(t, testbench.TCP{ - Flags: testbench.TCPFlags(tt.tcpFlags), - SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), - }, tt.payload...) - timeout := 3 * time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) - if err == nil { - t.Fatalf("expected no ACK packet but got one: %s", gotACK) - } - } - if !tt.expectACK && gotACK != nil { - t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go deleted file mode 100644 index 9054955ea..000000000 --- a/test/packetimpact/tests/tcp_paws_mechanism_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_paws_mechanism_test - -import ( - "encoding/hex" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestPAWSMechanism(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - options := make([]byte, header.TCPOptionTSLength) - header.EncodeTSOption(currentTS(), 0, options) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), Options: options}) - synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("didn't get synack during handshake: %s", err) - } - parsedSynOpts := header.ParseSynOptions(synAck.Options, true) - if !parsedSynOpts.TS { - t.Fatalf("expected TSOpt from DUT, options we got:\n%s", hex.Dump(synAck.Options)) - } - tsecr := parsedSynOpts.TSVal - header.EncodeTSOption(currentTS(), tsecr, options) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}) - acceptFD, _ := dut.Accept(t, listenFD) - defer dut.Close(t, acceptFD) - - sampleData := []byte("Sample Data") - sentTSVal := currentTS() - header.EncodeTSOption(sentTSVal, tsecr, options) - // 3ms here is chosen arbitrarily to make sure we have increasing timestamps - // every time we send one, it should not cause any flakiness because timestamps - // only need to be non-decreasing. - time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected an ACK but got none: %s", err) - } - - parsedOpts := header.ParseTCPOptions(gotTCP.Options) - if !parsedOpts.TS { - t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options)) - } - if parsedOpts.TSVal < tsecr { - t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr) - } - if parsedOpts.TSEcr != sentTSVal { - t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal) - } - tsecr = parsedOpts.TSVal - lastAckNum := gotTCP.AckNum - - badTSVal := sentTSVal - 100 - header.EncodeTSOption(badTSVal, tsecr, options) - // 3ms here is chosen arbitrarily and this time.Sleep() should not cause flakiness - // due to the exact same reasoning discussed above. - time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - - gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected segment with AckNum %d but got none: %s", lastAckNum, err) - } - parsedOpts = header.ParseTCPOptions(gotTCP.Options) - if !parsedOpts.TS { - t.Fatalf("expected TS option in response, options we got:\n%s", hex.Dump(gotTCP.Options)) - } - if parsedOpts.TSVal < tsecr { - t.Fatalf("TSVal should be non-decreasing, but %d < %d", parsedOpts.TSVal, tsecr) - } - if parsedOpts.TSEcr != sentTSVal { - t.Fatalf("TSEcr should match our sent TSVal, %d != %d", parsedOpts.TSEcr, sentTSVal) - } -} - -func currentTS() uint32 { - return uint32(time.Now().UnixNano() / 1e6) -} diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go deleted file mode 100644 index 1c8b72ebe..000000000 --- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_queue_send_recv_in_syn_sent_test - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "flag" - "sync" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestQueueSendInSynSentHandshake tests send behavior when the TCP state -// is SYN-SENT and the connections is finally established. -func TestQueueSendInSynSentHandshake(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - // Test blocking send. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue SEND call in SYN-SENT, this should be queued for - // process until the connection is established. - n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) - if n == -1 { - t.Errorf("failed to send on DUT: %s", err) - return - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) - // Expect the data from the DUT's enqueued send request. - // - // On Linux, this can be piggybacked with the ACK completing the - // handshake. On gVisor, getting such a piggyback is a bit more - // complicated because the actual data enqueuing occurs in the - // callers of endpoint Write. - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } -} - -// TestQueueRecvInSynSentHandshake tests recv behavior when the TCP state -// is SYN-SENT and the connections is finally established. -func TestQueueRecvInSynSentHandshake(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { - t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) - } - - // Test blocking read. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue RECEIVE call in SYN-SENT, this should be queued for - // process until the connection is established. - n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) - if n == -1 { - t.Errorf("failed to recv on DUT: %s", err) - return - } - if got := buff[:n]; !bytes.Equal(got, sampleData) { - t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData)) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } - - // Send sample payload so that DUT can recv. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK from DUT, but got none: %s", err) - } -} - -// TestQueueSendInSynSentRST tests send behavior when the TCP state -// is SYN-SENT and an RST is sent. -func TestQueueSendInSynSentRST(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - // Test blocking send. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue SEND call in SYN-SENT, this should be queued for - // process until the connection is established. - n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) - if err != unix.ECONNREFUSED { - t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) -} - -// TestQueueRecvInSynSentRST tests recv behavior when the TCP state -// is SYN-SENT and an RST is sent. -func TestQueueRecvInSynSentRST(t *testing.T) { - dut := testbench.NewDUT(t) - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - sampleData := []byte("Sample Data") - - dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { - t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { - t.Fatalf("expected a SYN from DUT, but got none: %s", err) - } - - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { - t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) - } - - // Test blocking read. - dut.SetNonBlocking(t, socket, false) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(1) - var block sync.WaitGroup - block.Add(1) - go func() { - defer wg.Done() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - block.Done() - // Issue RECEIVE call in SYN-SENT, this should be queued for - // process until the connection is established. - n, _, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) - if err != unix.ECONNREFUSED { - t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) - } - if n != -1 { - t.Errorf("expected return value %d, got %d", -1, n) - } - }() - - // Wait for the goroutine to be scheduled and before it - // blocks on endpoint send/receive. - block.Wait() - // The following sleep is used to prevent the connection - // from being established before we are blocked: there is - // still a small time window between we sending the RPC - // request and the system actually being blocked. - time.Sleep(100 * time.Millisecond) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) -} diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go deleted file mode 100644 index 0a5b0f12b..000000000 --- a/test/packetimpact/tests/tcp_rack_test.go +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_rack_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -const ( - // payloadSize is the size used to send packets. - payloadSize = header.TCPDefaultMSS - - // simulatedRTT is the time delay between packets sent and acked to - // increase the RTT. - simulatedRTT = 30 * time.Millisecond - - // numPktsForRTT is the number of packets sent and acked to establish - // RTT. - numPktsForRTT = 10 -) - -func createSACKConnection(t *testing.T) (testbench.DUT, testbench.TCPIPv4, int32, int32) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - - // Enable SACK. - opts := make([]byte, 40) - optsOff := 0 - optsOff += header.EncodeNOP(opts[optsOff:]) - optsOff += header.EncodeNOP(opts[optsOff:]) - optsOff += header.EncodeSACKPermittedOption(opts[optsOff:]) - - conn.ConnectWithOptions(t, opts[:optsOff]) - acceptFd, _ := dut.Accept(t, listenFd) - return dut, conn, acceptFd, listenFd -} - -func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, acceptFd, listenFd int32) { - dut.Close(t, acceptFd) - dut.Close(t, listenFd) - conn.Close(t) -} - -func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) { - info := linux.TCPInfo{} - infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) - if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want { - t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want) - } - binary.Unmarshal(infoBytes, usermem.ByteOrder, &info) - return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond -} - -func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, numPkts int, acceptFd int32, sendACK bool) time.Time { - seqNum1 := *conn.RemoteSeqNum(t) - payload := make([]byte, payloadSize) - var lastSent time.Time - for i, sn := 0, seqNum1; i < numPkts; i++ { - lastSent = time.Now() - dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second) - if err != nil { - t.Fatalf("Expect #%d: %s", i+1, err) - continue - } - if gotOne == nil { - t.Fatalf("#%d: expected a packet within a second but got none", i+1) - } - sn.UpdateForward(seqnum.Size(payloadSize)) - - if sendACK { - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))}) - } - } - return lastSent -} - -// TestRACKTLPAllPacketsLost tests TLP when an entire flight of data is lost. -func TestRACKTLPAllPacketsLost(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 5 - lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // Probe Timeout (PTO) should be two times RTT. Check that the last - // packet is retransmitted after probe timeout. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - pto := rtt * 2 - // We expect the 5th packet (the last unacknowledged packet) to be - // retransmitted. - tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s %v %v", err, rtt, pto) - } - diff := time.Now().Sub(lastSent) - if diff < pto { - t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKTLPLost tests TLP when there are tail losses. -// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.4 -func TestRACKTLPLost(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 10 - lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // Cumulative ACK for #[1-5] packets. - ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize)) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))}) - - // Probe Timeout (PTO) should be two times RTT. Check that the last - // packet is retransmitted after probe timeout. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - pto := rtt * 2 - // We expect the 10th packet (the last unacknowledged packet) to be - // retransmitted. - tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - diff := time.Now().Sub(lastSent) - if diff < pto { - t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithSACK tests that RACK marks the packets as lost after receiving -// the ACK for retransmitted packets. -// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1 -func TestRACKWithSACK(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 3 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // SACK for #2 packet. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(payloadSize)) - end := start.Add(seqnum.Size(payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - // RACK marks #1 packet as lost after RTT+reorderWindow(RTT/4) and - // retransmits it. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - time.Sleep(simulatedRTT) - // ACK for #1 packet. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))}) - - // RACK considers transmission times of the packets to mark them lost. - // As the 3rd packet was sent before the retransmitted 1st packet, RACK - // marks it as lost and retransmits it.. - expectedSeqNum := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: expectedSeqNum}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithoutReorder tests that without reordering RACK will retransmit the -// lost packets after reorder timer expires. -func TestRACKWithoutReorder(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 4 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // SACK for [3,4] packets. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(2 * payloadSize)) - end := start.Add(seqnum.Size(2 * payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // RACK marks #1 and #2 packets as lost and retransmits both after - // RTT + reorderWindow. The reorderWindow initially will be a small - // fraction of RTT. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - for i, sn := 0, seqNum1; i < 2; i++ { - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - sn.UpdateForward(seqnum.Size(payloadSize)) - } - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithReorder tests that RACK will retransmit segments when there is -// reordering in the connection and reorder timer expires. -func TestRACKWithReorder(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 4 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // SACK in reverse order for the connection to detect reorder. - var start seqnum.Value - var end seqnum.Value - for i := 0; i < numPkts-1; i++ { - sackBlock := make([]byte, 40) - sbOff := 0 - start = seqNum1.Add(seqnum.Size((numPkts - i - 1) * payloadSize)) - end = start.Add(seqnum.Size((i + 1) * payloadSize)) - sackBlock = make([]byte, 40) - sbOff = 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - } - - // Send a DSACK block indicating both original and retransmitted - // packets are received, RACK will increase the reordering window on - // every DSACK. - dsackBlock := make([]byte, 40) - dbOff := 0 - start = seqNum1 - end = start.Add(seqnum.Size(2 * payloadSize)) - dbOff += header.EncodeNOP(dsackBlock[dbOff:]) - dbOff += header.EncodeNOP(dsackBlock[dbOff:]) - dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, dsackBlock[dbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]}) - - seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize)) - sendTime := time.Now() - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - time.Sleep(simulatedRTT) - // Send SACK for [2-5] packets. - sackBlock := make([]byte, 40) - sbOff := 0 - start = seqNum1.Add(seqnum.Size(payloadSize)) - end = start.Add(seqnum.Size(3 * payloadSize)) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // Expect the retransmission of #1 packet after RTT+ReorderWindow. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - diff := time.Now().Sub(sendTime) - if diff < rtt { - t.Fatalf("expected payload was received too sonn, within RTT") - } - - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} - -// TestRACKWithLostRetransmission tests that RACK will not enter RTO when a -// retransmitted segment is lost and enters fast recovery. -func TestRACKWithLostRetransmission(t *testing.T) { - dut, conn, acceptFd, listenFd := createSACKConnection(t) - seqNum1 := *conn.RemoteSeqNum(t) - - // Send ACK for data packets to establish RTT. - sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */) - seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize)) - - // We are not sending ACK for these packets. - const numPkts = 5 - sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) - - // SACK for [2-5] packets. - sackBlock := make([]byte, 40) - start := seqNum1.Add(seqnum.Size(payloadSize)) - end := start.Add(seqnum.Size(4 * payloadSize)) - sbOff := 0 - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeNOP(sackBlock[sbOff:]) - sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock[sbOff:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - - // RACK marks #1 packet as lost and retransmits it after - // RTT + reorderWindow. The reorderWindow is bounded between a small - // fraction of RTT and 1 RTT. - rtt, _ := getRTTAndRTO(t, dut, acceptFd) - timeout := 2 * rtt - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Send #6 packet. - payload := make([]byte, payloadSize) - dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1 + 5*payloadSize))}, time.Second) - if err != nil { - t.Fatalf("Expect #6: %s", err) - } - if gotOne == nil { - t.Fatalf("#6: expected a packet within a second but got none") - } - - // SACK for [2-6] packets. - sackBlock1 := make([]byte, 40) - start = seqNum1.Add(seqnum.Size(payloadSize)) - end = start.Add(seqnum.Size(5 * payloadSize)) - sbOff1 := 0 - sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:]) - sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:]) - sbOff1 += header.EncodeSACKBlocks([]header.SACKBlock{{ - start, end, - }}, sackBlock1[sbOff1:]) - time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]}) - - // Expect re-retransmission of #1 packet without entering an RTO. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check the congestion control state. - info := linux.TCPInfo{} - infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) - if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want { - t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want) - } - binary.Unmarshal(infoBytes, usermem.ByteOrder, &info) - if info.CaState != linux.TCP_CA_Recovery { - t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState) - } - - closeSACKConnection(t, dut, conn, acceptFd, listenFd) -} diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go deleted file mode 100644 index f121d44eb..000000000 --- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_rcv_buf_space_test - -import ( - "context" - "flag" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestReduceRecvBuf tests that a packet within window is still dropped -// if the available buffer space drops below the size of the incoming -// segment. -func TestReduceRecvBuf(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - // Set a small receive buffer for the test. - const rcvBufSz = 4096 - dut.SetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF, rcvBufSz) - - // Retrieve the actual buffer. - bufSz := dut.GetSockOptInt(t, acceptFd, unix.SOL_SOCKET, unix.SO_RCVBUF) - - // Generate a payload of 1 more than the actual buffer size used by the - // DUT. - sampleData := testbench.GenerateRandomPayload(t, int(bufSz)+1) - // Send and receive sample data to the dut. - const pktSize = 1400 - for payload := sampleData; len(payload) != 0; { - payloadBytes := pktSize - if l := len(payload); l < payloadBytes { - payloadBytes = l - } - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...) - payload = payload[payloadBytes:] - } - - // First read should read < len(sampleData) - if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), 0); ret == -1 || int(ret) == len(sampleData) { - t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, int32(len(sampleData)), ret, err) - } - - // Second read should return EAGAIN as the last segment should have been - // dropped due to it exceeding the receive buffer space available in the - // socket. - if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), unix.MSG_DONTWAIT); got != nil || ret != -1 || err != unix.EAGAIN { - t.Fatalf("expected no packets but got: %s", got) - } -} diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go deleted file mode 100644 index 3dc8f63ab..000000000 --- a/test/packetimpact/tests/tcp_retransmits_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_retransmits_test - -import ( - "bytes" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func getRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rto time.Duration) { - info := linux.TCPInfo{} - infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) - if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want { - t.Fatalf("unexpected size for TCP_INFO, got %d bytes want %d bytes", got, want) - } - binary.Unmarshal(infoBytes, usermem.ByteOrder, &info) - return time.Duration(info.RTO) * time.Microsecond -} - -// TestRetransmits tests retransmits occur at exponentially increasing -// time intervals. -func TestRetransmits(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. - // This is to reduce the test run-time from the default initial RTO of 1s. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip this data send/recv which is solely to estimate RTO. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // Wait for the DUT to receive the data, thus ensuring that the stack has - // estimated RTO before we query RTO via TCP_INFO. - if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) - } - - const timeoutCorrection = time.Second - const diffCorrection = 200 * time.Millisecond - rto := getRTO(t, dut, acceptFd) - - dut.Send(t, acceptFd, sampleData, 0) - seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Expect retransmits of the same segment. - for i := 0; i < 5; i++ { - startTime := time.Now() - rto = getRTO(t, dut, acceptFd) - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { - t.Fatalf("expected payload was not received within %s loop %d err %s", rto+timeoutCorrection, i, err) - } - if diff := time.Since(startTime); diff+diffCorrection < rto { - t.Fatalf("retransmit came sooner got: %s want: >= %s probe %d", diff+diffCorrection, rto, i) - } - } -} diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go deleted file mode 100644 index 64b7288fb..000000000 --- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_send_window_sizes_piggyback_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestSendWindowSizesPiggyback tests cases where segment sizes are close to -// sender window size and checks for ACK piggybacking for each of those case. -func TestSendWindowSizesPiggyback(t *testing.T) { - sampleData := []byte("Sample Data") - segmentSize := uint16(len(sampleData)) - // Advertise receive window sizes that are lesser, equal to or greater than - // enqueued segment size and check for segment transmits. The test attempts - // to enqueue a segment on the dut before acknowledging previous segment and - // lets the dut piggyback any ACKs along with the enqueued segment. - for _, tt := range []struct { - description string - windowSize uint16 - expectedPayload1 []byte - expectedPayload2 []byte - enqueue bool - }{ - // Expect the first segment to be split as it cannot be accomodated in - // the sender window. This means we need not enqueue a new segment after - // the first segment. - {"WindowSmallerThanSegment", segmentSize - 1, sampleData[:(segmentSize - 1)], sampleData[(segmentSize - 1):], false /* enqueue */}, - - {"WindowEqualToSegment", segmentSize, sampleData, sampleData, true /* enqueue */}, - - // Expect the second segment to not be split as its size is greater than - // the available sender window size. The segments should not be split - // when there is pending unacknowledged data and the segment-size is - // greater than available sender window. - {"WindowGreaterThanSegment", segmentSize + 1, sampleData, sampleData, true /* enqueue */}, - } { - t.Run(tt.description, func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - expectedTCP := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)} - - dut.Send(t, acceptFd, sampleData, 0) - expectedPayload := testbench.Payload{Bytes: tt.expectedPayload1} - if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Expect any enqueued segment to be transmitted by the dut along with - // piggybacked ACK for our data. - - if tt.enqueue { - // Enqueue a segment for the dut to transmit. - dut.Send(t, acceptFd, sampleData, 0) - } - - // Send ACK for the previous segment along with data for the dut to - // receive and ACK back. Sending this ACK would make room for the dut - // to transmit any enqueued segment. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData}) - - // Expect the dut to piggyback the ACK for received data along with - // the segment enqueued for transmit. - expectedPayload = testbench.Payload{Bytes: tt.expectedPayload2} - if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go deleted file mode 100644 index 3346d43c4..000000000 --- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_syn_reset_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTCPSynRcvdReset tests transition from SYN-RCVD to CLOSED. -func TestTCPSynRcvdReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - // Expect dut connection to have transitioned to SYN-RCVD state. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - - // Expect the connection to have transitioned SYN-RCVD to CLOSED. - // - // Retransmit the ACK a few times to give time for the DUT to transition to - // CLOSED. We cannot use TCP_INFO to lookup the state as this is a passive - // DUT connection. - for i := 0; i < 5; i++ { - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Logf("retransmit%d ACK as we did not get the expected RST, %s", i, err) - continue - } - return - } - t.Fatal("did not receive a TCP RST") -} diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go deleted file mode 100644 index cccb0abc6..000000000 --- a/test/packetimpact/tests/tcp_synsent_reset_test.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 tcp_synsent_reset_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// dutSynSentState sets up the dut connection in SYN-SENT state. -func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, uint16) { - t.Helper() - - dut := testbench.NewDUT(t) - - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) - port := uint16(9001) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) - - sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], dut.Net.LocalIPv4) - // Bring the dut to SYN-SENT state with a non-blocking connect. - dut.Connect(t, clientFD, &sa) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN\n") - } - - return &dut, &conn, port, clientPort -} - -// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition. -func TestTCPSynSentReset(t *testing.T) { - _, conn, _, _ := dutSynSentState(t) - defer conn.Close(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) - // Expect the connection to have closed. - // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } -} - -// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED -// transitions. -func TestTCPSynSentRcvdReset(t *testing.T) { - dut, c, remotePort, clientPort := dutSynSentState(t) - defer c.Close(t) - - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort}) - defer conn.Close(t) - // Initiate new SYN connection with the same port pair - // (simultaneous open case), expect the dut connection to move to - // SYN-RCVD state - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK %s\n", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - // Expect the connection to have transitioned SYN-RCVD to CLOSED. - // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } -} diff --git a/test/packetimpact/tests/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go deleted file mode 100644 index 89037f0a4..000000000 --- a/test/packetimpact/tests/tcp_timewait_reset_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_timewait_reset_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestTimeWaitReset tests handling of RST when in TIME_WAIT state. -func TestTimeWaitReset(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Close(t, acceptFD) - - _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Send a FIN, DUT should transition to TIME_WAIT from FIN_WAIT2. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK for our FIN: %s", err) - } - - // Send a RST, the DUT should transition to CLOSED from TIME_WAIT. - // This is the default Linux behavior, it can be changed to ignore RSTs via - // sysctl net.ipv4.tcp_rfc1337. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // The DUT should reply with RST to our ACK as the state should have - // transitioned to CLOSED. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected a RST: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go deleted file mode 100644 index a208210ac..000000000 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_unacc_seq_ack_closing_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestSimultaneousCloseUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - // Do not ack the FIN from DUT so that we get to CLOSING. - seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) - conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ACK to our FIN, but got none: %s", err) - } - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - origSeq := uint32(*conn.LocalSeqNum(t)) - // Send a segment with OTW Seq / unacc ACK. - tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)) - if tt.description == "OTWSeq" { - // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that - // we stay in CLOSING. - tcp.AckNum = seqNumForTheirFIN - } - conn.Send(t, tcp, samplePayload) - - got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Errorf("expected an ack in CLOSING state, but got none: %s", err) - } - if !tt.expectAck && got != nil { - t.Errorf("expected no ack in CLOSING state, but got one: %s", got) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go deleted file mode 100644 index ce0a26171..000000000 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_unacc_seq_ack_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/seqnum" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestEstablishedUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - restoreSeq bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: true, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: false, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: false, restoreSeq: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - dut.Accept(t, listenFD) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected ack %s", err) - } - windowSize := seqnum.Size(*gotTCP.WindowSize) - - origSeq := *conn.LocalSeqNum(t) - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload) - if tt.restoreSeq { - // Restore the local sequence number to ensure that the incoming - // ACK matches the TCP layer state. - *conn.LocalSeqNum(t) = origSeq - } - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Fatalf("expected an ack but got none: %s", err) - } - if err == nil && !tt.expectAck && gotAck != nil { - t.Fatalf("expected no ack but got one: %s", gotAck) - } - }) - } -} - -func TestPassiveCloseUnaccSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - expectAck bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Send a FIN to DUT to intiate the passive close. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)}) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err) - } - windowSize := seqnum.Size(*gotTCP.WindowSize) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload) - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if tt.expectAck && err != nil { - t.Errorf("expected an ack but got none: %s", err) - } - if err == nil && !tt.expectAck && gotAck != nil { - t.Errorf("expected no ack but got one: %s", gotAck) - } - - // Now let's verify DUT is indeed in CLOSE_WAIT - dut.Close(t, acceptFD) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil { - t.Fatalf("expected DUT to send a FIN: %s", err) - } - // Ack the FIN from DUT - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Send some extra data to DUT - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, samplePayload) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { - t.Fatalf("expected DUT to send an RST: %s", err) - } - }) - } -} - -func TestActiveCloseUnaccpSeqAck(t *testing.T) { - for _, tt := range []struct { - description string - makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP - seqNumOffset seqnum.Size - restoreSeq bool - }{ - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, restoreSeq: true}, - } { - t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - // Trigger active close. - dut.Shutdown(t, acceptFD, unix.SHUT_WR) - - // Get to FIN_WAIT2 - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected a FIN: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - sendUnaccSeqAck := func(state string) { - t.Helper() - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - origSeq := *conn.LocalSeqNum(t) - // Send a segment with OTW Seq / unacc ACK. - conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)), samplePayload) - if tt.restoreSeq { - // Restore the local sequence number to ensure that the - // incoming ACK matches the TCP layer state. - *conn.LocalSeqNum(t) = origSeq - } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Errorf("expected an ack in %s state, but got none: %s", state, err) - } - } - - sendUnaccSeqAck("FIN_WAIT2") - - // Send a FIN to DUT to get to TIME_WAIT - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { - t.Fatalf("expected an ACK for our fin and DUT should enter TIME_WAIT: %s", err) - } - - sendUnaccSeqAck("TIME_WAIT") - }) - } -} diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go deleted file mode 100644 index ef38bd738..000000000 --- a/test/packetimpact/tests/tcp_user_timeout_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_user_timeout_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) { - sampleData := make([]byte, 100) - for i := range sampleData { - sampleData[i] = uint8(i) - } - conn.Drain(t) - dut.Send(t, fd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { - t.Fatalf("expected data but got none: %w", err) - } -} - -func sendFIN(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) { - dut.Close(t, fd) -} - -func TestTCPUserTimeout(t *testing.T) { - for _, tt := range []struct { - description string - userTimeout time.Duration - sendDelay time.Duration - }{ - {"NoUserTimeout", 0, 3 * time.Second}, - {"ACKBeforeUserTimeout", 5 * time.Second, 4 * time.Second}, - {"ACKAfterUserTimeout", 5 * time.Second, 7 * time.Second}, - } { - for _, ttf := range []struct { - description string - f func(_ *testing.T, _ *testbench.TCPIPv4, _ *testbench.DUT, fd int32) - }{ - {"AfterPayload", sendPayload}, - {"AfterFIN", sendFIN}, - } { - t.Run(tt.description+ttf.description, func(t *testing.T) { - // Create a socket, listen, TCP handshake, and accept. - dut := testbench.NewDUT(t) - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFD) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - conn.Connect(t) - acceptFD, _ := dut.Accept(t, listenFD) - - if tt.userTimeout != 0 { - dut.SetSockOptInt(t, acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds())) - } - - ttf.f(t, &conn, &dut, acceptFD) - - time.Sleep(tt.sendDelay) - conn.Drain(t) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - // If TCP_USER_TIMEOUT was set and the above delay was longer than the - // TCP_USER_TIMEOUT then the DUT should send a RST in response to the - // testbench's packet. - expectRST := tt.userTimeout != 0 && tt.sendDelay > tt.userTimeout - expectTimeout := 5 * time.Second - got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, expectTimeout) - if expectRST && err != nil { - t.Errorf("expected RST packet within %s but got none: %s", expectTimeout, err) - } - if !expectRST && got != nil { - t.Errorf("expected no RST packet within %s but got one: %s", expectTimeout, got) - } - }) - } - } -} diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go deleted file mode 100644 index 0d65a2ea2..000000000 --- a/test/packetimpact/tests/tcp_window_shrink_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_window_shrink_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestWindowShrink(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - dut.Send(t, acceptFd, sampleData, 0) - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - // We close our receiving window here - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - dut.Send(t, acceptFd, []byte("Sample Data"), 0) - // Note: There is another kind of zero-window probing which Windows uses (by sending one - // new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change - // the following lines. - expectedRemoteSeqNum := *conn.RemoteSeqNum(t) - 1 - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", expectedRemoteSeqNum, err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go deleted file mode 100644 index d73495454..000000000 --- a/test/packetimpact/tests/tcp_zero_receive_window_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_zero_receive_window_test - -import ( - "flag" - "fmt" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroReceiveWindow tests if the DUT sends a zero receive window eventually. -func TestZeroReceiveWindow(t *testing.T) { - for _, payloadLen := range []int{64, 512, 1024} { - t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - // Expect the DUT to eventually advertise zero receive window. - // The test would timeout otherwise. - for readOnce := false; ; { - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // 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 - } - } - }) - } -} - -// TestNonZeroReceiveWindow tests for the DUT to never send a zero receive -// window when the data is being read from the socket buffer. -func TestNonZeroReceiveWindow(t *testing.T) { - for _, payloadLen := range []int{64, 512, 1024} { - t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} - var rcvWindow uint16 - initRcv := false - // This loop keeps a running rcvWindow value from the initial ACK for the data - // we sent. Once we have received ACKs with non-zero receive windows, we break - // the loop. - for { - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) - if err != nil { - t.Fatalf("expected packet was not received: %s", 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.") - } - if !initRcv { - rcvWindow = uint16(*gotTCP.WindowSize) - initRcv = true - } - if rcvWindow <= uint16(payloadLen) { - break - } - rcvWindow -= uint16(payloadLen) - } - }) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go deleted file mode 100644 index 22b17a39e..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_zero_window_probe_retransmit_test - -import ( - "bytes" - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbeRetransmit tests retransmits of zero window probes -// to be sent at exponentially inreasing time intervals. -func TestZeroWindowProbeRetransmit(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Check for the dut to keep the connection alive as long as the zero window - // probes are acknowledged. Check if the zero window probes are sent at - // exponentially increasing intervals. The timeout intervals are function - // of the recorded first zero probe transmission duration. - // - // Advertize zero receive window along with a payload. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(0)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - // Wait for the payload to be received by the DUT, which is also an - // indication of receive of the peer window advertisement. - if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { - t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) - } - - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - - var prev time.Duration - // Expect the dut to keep the connection alive as long as the remote is - // acknowledging the zero-window probes. - for i := 1; i <= 5; i++ { - start := time.Now() - // Expect zero-window probe with a timeout which is a function of the typical - // first retransmission time. The retransmission times is supposed to - // exponentially increase. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Duration(i)*time.Second); err != nil { - t.Fatalf("%d: expected a probe with sequence number %d: %s", i, probeSeq, err) - } - if i == 1 { - // Skip the first probe as computing transmit time for that is - // non-deterministic because of the arbitrary time taken for - // the dut to receive a send command and issue a send. - continue - } - - // Check if the time taken to receive the probe from the dut is - // increasing exponentially. To avoid flakes, use a correction - // factor for the expected duration which accounts for any - // scheduling non-determinism. - const timeCorrection = 200 * time.Millisecond - got := time.Since(start) - if want := (2 * prev) - timeCorrection; prev != 0 && got < want { - t.Errorf("got zero probe %d after %s, want >= %s", i, got, want) - } - prev = got - // Acknowledge the zero-window probes from the dut. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - } - // Advertize non-zero window. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the dut to recover and transmit data. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go deleted file mode 100644 index 8b90fcbe9..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_zero_window_probe_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbe tests few cases of zero window probing over the -// same connection. -func TestZeroWindowProbe(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - start := time.Now() - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - sendTime := time.Now().Sub(start) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - - // Test 1: Check for receive of a zero window probe, record the duration for - // probe to be sent. - // - // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - // Expected sequence number of the zero window probe. - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - // Expected ack number of the ACK for the probe. - ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - - // Expect there are no zero-window probes sent until there is data to be sent out - // from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil { - t.Fatalf("unexpected packet with sequence number %d: %s", probeSeq, err) - } - - start = time.Now() - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - // Expect zero-window probe from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err) - } - // Expect the probe to be sent after some time. Compare against the previous - // time recorded when the dut immediately sends out data on receiving the - // send command. - if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime { - t.Fatalf("expected the first probe to be sent out after retransmission interval, got %s want > %s", startProbeDuration, sendTime) - } - - // Test 2: Check if the dut recovers on advertizing non-zero receive window. - // and sends out the sample payload after the send window opens. - // - // Advertize non-zero window to the dut and ack the zero window probe. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Expect the dut to recover and transmit data. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - - // Test 3: Sanity check for dut's processing of a similar probe it sent. - // Check if the dut responds as we do for a similar probe sent to it. - // Basically with sequence number to one byte behind the unacknowledged - // sequence number. - p := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with ack number: %d: %s", p, err) - } -} diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go deleted file mode 100644 index 1ce4d22b7..000000000 --- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tcp_zero_window_probe_usertimeout_test - -import ( - "flag" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are -// retransmitting zero window probes. -func TestZeroWindowProbeUserTimeout(t *testing.T) { - dut := testbench.NewDUT(t) - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - defer dut.Close(t, listenFd) - conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer conn.Close(t) - - conn.Connect(t) - acceptFd, _ := dut.Accept(t, listenFd) - defer dut.Close(t, acceptFd) - - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - - sampleData := []byte("Sample Data") - samplePayload := &testbench.Payload{Bytes: sampleData} - - // Send and receive sample data to the dut. - dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { - t.Fatalf("expected payload was not received: %s", err) - } - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } - - // Test 1: Check for receive of a zero window probe, record the duration for - // probe to be sent. - // - // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - - // Expected sequence number of the zero window probe. - probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) - start := time.Now() - // Ask the dut to send out data. - dut.Send(t, acceptFd, sampleData, 0) - // Expect zero-window probe from the dut. - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil { - t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err) - } - // Record the duration for first probe, the dut sends the zero window probe after - // a retransmission time interval. - startProbeDuration := time.Now().Sub(start) - - // Test 2: Check if the dut times out the connection by honoring usertimeout - // when the dut is sending zero-window probes. - // - // Reduce the retransmit timeout. - dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds())) - // Advertize zero window again. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) - // Ask the dut to send out data that would trigger zero window probe retransmissions. - dut.Send(t, acceptFd, sampleData, 0) - - // Wait for the connection to timeout after multiple zero-window probe retransmissions. - time.Sleep(8 * startProbeDuration) - - // Expect the connection to have timed out and closed which would cause the dut - // to reply with a RST to the ACK we send. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { - t.Fatalf("expected a TCP RST") - } -} diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go deleted file mode 100644 index f4ae00a81..000000000 --- a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_any_addr_recv_unicast_test - -import ( - "flag" - "net" - "testing" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestAnyRecvUnicastUDP(t *testing.T) { - dut := testbench.NewDUT(t) - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, boundFD) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) - conn.SendIP( - t, - testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(dut.Net.RemoteIPv4))}, - testbench.UDP{}, - &testbench.Payload{Bytes: payload}, - ) - got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } -} diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go deleted file mode 100644 index f63cfcc9a..000000000 --- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_discard_mcast_source_addr_test - -import ( - "context" - "flag" - "fmt" - "net" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -var oneSecond = unix.Timeval{Sec: 1, Usec: 0} - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) { - dut := testbench.NewDUT(t) - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4) - defer dut.Close(t, remoteFD) - dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - for _, mcastAddr := range []net.IP{ - net.IPv4allsys, - net.IPv4allrouter, - net.IPv4(224, 0, 1, 42), - net.IPv4(232, 1, 2, 3), - } { - t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) { - conn.SendIP( - t, - testbench.IPv4{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To4()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: []byte("test payload")}, - ) - - ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) - } - }) - } -} - -func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) { - dut := testbench.NewDUT(t) - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv6) - defer dut.Close(t, remoteFD) - dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - for _, mcastAddr := range []net.IP{ - net.IPv6interfacelocalallnodes, - net.IPv6linklocalallnodes, - net.IPv6linklocalallrouters, - net.ParseIP("ff01::42"), - net.ParseIP("ff02::4242"), - } { - t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) { - conn.SendIPv6( - t, - testbench.IPv6{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To16()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: []byte("test payload")}, - ) - ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) - } - }) - } -} diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go deleted file mode 100644 index 3159d5b89..000000000 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_icmp_error_propagation_test - -import ( - "context" - "flag" - "fmt" - "net" - "sync" - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -type connectionMode bool - -func (c connectionMode) String() string { - if c { - return "Connected" - } - return "Connectionless" -} - -type icmpError int - -const ( - portUnreachable icmpError = iota - timeToLiveExceeded -) - -func (e icmpError) String() string { - switch e { - case portUnreachable: - return "PortUnreachable" - case timeToLiveExceeded: - return "TimeToLiveExpired" - } - return "Unknown ICMP error" -} - -func (e icmpError) ToICMPv4() *testbench.ICMPv4 { - switch e { - case portUnreachable: - return &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4PortUnreachable)} - case timeToLiveExceeded: - return &testbench.ICMPv4{ - Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded), - Code: testbench.ICMPv4Code(header.ICMPv4TTLExceeded)} - } - return nil -} - -type errorDetection struct { - name string - useValidConn bool - f func(context.Context, *testing.T, testData) -} - -type testData struct { - dut *testbench.DUT - conn *testbench.UDPIPv4 - remoteFD int32 - remotePort uint16 - cleanFD int32 - cleanPort uint16 - wantErrno unix.Errno -} - -// wantErrno computes the errno to expect given the connection mode of a UDP -// socket and the ICMP error it will receive. -func wantErrno(c connectionMode, icmpErr icmpError) unix.Errno { - if c && icmpErr == portUnreachable { - return unix.ECONNREFUSED - } - return unix.Errno(0) -} - -// sendICMPError sends an ICMP error message in response to a UDP datagram. -func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp *testbench.UDP) { - t.Helper() - - layers := conn.CreateFrame(t, nil) - layers = layers[:len(layers)-1] - ip, ok := udp.Prev().(*testbench.IPv4) - if !ok { - t.Fatalf("expected %s to be IPv4", udp.Prev()) - } - if icmpErr == timeToLiveExceeded { - *ip.TTL = 1 - // Let serialization recalculate the checksum since we set the TTL - // to 1. - ip.Checksum = nil - } - // Note that the ICMP payload is valid in this case because the UDP - // payload is empty. If the UDP payload were not empty, the packet - // length during serialization may not be calculated correctly, - // resulting in a mal-formed packet. - layers = append(layers, icmpErr.ToICMPv4(), ip, udp) - - conn.SendFrameStateless(t, layers) -} - -// testRecv tests observing the ICMP error through the recv unix. A packet -// is sent to the DUT, and if wantErrno is non-zero, then the first recv should -// fail and the second should succeed. Otherwise if wantErrno is zero then the -// first recv should succeed immediately. -func testRecv(ctx context.Context, t *testing.T, d testData) { - t.Helper() - - // Check that receiving on the clean socket works. - d.conn.Send(t, testbench.UDP{DstPort: &d.cleanPort}) - d.dut.Recv(t, d.cleanFD, 100, 0) - - d.conn.Send(t, testbench.UDP{}) - - if d.wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0) - if ret != -1 { - t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno) - } - if err != d.wantErrno { - t.Fatalf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno) - } - } - - d.dut.Recv(t, d.remoteFD, 100, 0) -} - -// testSendTo tests observing the ICMP error through the send syscall. If -// wantErrno is non-zero, the first send should fail and a subsequent send -// should suceed; while if wantErrno is zero then the first send should just -// succeed. -func testSendTo(ctx context.Context, t *testing.T, d testData) { - // Check that sending on the clean socket works. - d.dut.SendTo(t, d.cleanFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet from clean socket on DUT: %s", err) - } - - if d.wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - - if ret != -1 { - t.Fatalf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno) - } - if err != d.wantErrno { - t.Fatalf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno) - } - } - - d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet as expected: %s", err) - } -} - -func testSockOpt(_ context.Context, t *testing.T, d testData) { - // Check that there's no pending error on the clean socket. - if errno := unix.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != unix.Errno(0) { - t.Fatalf("unexpected error (%[1]d) %[1]v on clean socket", errno) - } - - if errno := unix.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno { - t.Fatalf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno) - } - - // Check that after clearing socket error, sending doesn't fail. - d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) - if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil { - t.Fatalf("did not receive UDP packet as expected: %s", err) - } -} - -// TestUDPICMPErrorPropagation tests that ICMP error messages in response to -// UDP datagrams are processed correctly. RFC 1122 section 4.1.3.3 states that: -// "UDP MUST pass to the application layer all ICMP error messages that it -// receives from the IP layer." -// -// The test cases are parametrized in 3 dimensions: 1. the UDP socket is either -// put into connection mode or left connectionless, 2. the ICMP message type -// and code, and 3. the method by which the ICMP error is observed on the -// socket: sendto, recv, or getsockopt(SO_ERROR). -// -// Linux's udp(7) man page states: "All fatal errors will be passed to the user -// as an error return even when the socket is not connected. This includes -// asynchronous errors received from the network." In practice, the only -// combination of parameters to the test that causes an error to be observable -// on the UDP socket is receiving a port unreachable message on a connected -// socket. -func TestUDPICMPErrorPropagation(t *testing.T) { - for _, connect := range []connectionMode{true, false} { - for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} { - wantErrno := wantErrno(connect, icmpErr) - - for _, errDetect := range []errorDetection{ - {"SendTo", false, testSendTo}, - // Send to an address that's different from the one that caused an ICMP - // error to be returned. - {"SendToValid", true, testSendTo}, - {"Recv", false, testRecv}, - {"SockOpt", false, testSockOpt}, - } { - t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) { - dut := testbench.NewDUT(t) - - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, remoteFD) - - // Create a second, clean socket on the DUT to ensure that the ICMP - // error messages only affect the sockets they are intended for. - cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, cleanFD) - - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - if connect { - dut.Connect(t, remoteFD, conn.LocalAddr(t)) - dut.Connect(t, cleanFD, conn.LocalAddr(t)) - } - - dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t)) - udp, err := conn.Expect(t, testbench.UDP{}, time.Second) - if err != nil { - t.Fatalf("did not receive message from DUT: %s", err) - } - - sendICMPError(t, &conn, icmpErr, udp) - - errDetectConn := &conn - if errDetect.useValidConn { - // connClean is a UDP socket on the test runner that was not - // involved in the generation of the ICMP error. As such, - // interactions between it and the the DUT should be independent of - // the ICMP error at least at the port level. - connClean := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer connClean.Close(t) - - errDetectConn = &connClean - } - - errDetect.f(context.Background(), t, testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno}) - }) - } - } - } -} - -// TestICMPErrorDuringUDPRecv tests behavior when a UDP socket is in the middle -// of a blocking recv and receives an ICMP error. -func TestICMPErrorDuringUDPRecv(t *testing.T) { - for _, connect := range []connectionMode{true, false} { - for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} { - wantErrno := wantErrno(connect, icmpErr) - - t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) { - dut := testbench.NewDUT(t) - - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, remoteFD) - - // Create a second, clean socket on the DUT to ensure that the ICMP - // error messages only affect the sockets they are intended for. - cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) - defer dut.Close(t, cleanFD) - - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - if connect { - dut.Connect(t, remoteFD, conn.LocalAddr(t)) - dut.Connect(t, cleanFD, conn.LocalAddr(t)) - } - - dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t)) - udp, err := conn.Expect(t, testbench.UDP{}, time.Second) - if err != nil { - t.Fatalf("did not receive message from DUT: %s", err) - } - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - - if wantErrno != unix.Errno(0) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0) - if ret != -1 { - t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno) - return - } - if err != wantErrno { - t.Errorf("recv during ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, wantErrno) - return - } - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0); ret == -1 { - t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err) - } - }() - - go func() { - defer wg.Done() - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if ret, _, err := dut.RecvWithErrno(ctx, t, cleanFD, 100, 0); ret == -1 { - t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err) - } - }() - - // TODO(b/155684889) This sleep is to allow time for the DUT to - // actually call recv since we want the ICMP error to arrive during the - // blocking recv, and should be replaced when a better synchronization - // alternative is available. - time.Sleep(2 * time.Second) - - sendICMPError(t, &conn, icmpErr, udp) - - conn.Send(t, testbench.UDP{DstPort: &cleanPort}) - conn.Send(t, testbench.UDP{}) - wg.Wait() - }) - } - } -} diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go deleted file mode 100644 index 230b012c7..000000000 --- a/test/packetimpact/tests/udp_send_recv_dgram_test.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_send_recv_dgram_test - -import ( - "context" - "flag" - "fmt" - "net" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) - testbench.RPCTimeout = 500 * time.Millisecond -} - -type udpConn interface { - SrcPort(*testing.T) uint16 - SendFrame(*testing.T, testbench.Layers, ...testbench.Layer) - ExpectFrame(*testing.T, testbench.Layers, time.Duration) (testbench.Layers, error) - Close(*testing.T) -} - -type testCase struct { - bindTo, sendTo net.IP - sendToBroadcast, bindToDevice, expectData bool -} - -func TestUDP(t *testing.T) { - dut := testbench.NewDUT(t) - subnetBcast := func() net.IP { - subnet := (&tcpip.AddressWithPrefix{ - Address: tcpip.Address(dut.Net.RemoteIPv4.To4()), - PrefixLen: dut.Net.IPv4PrefixLength, - }).Subnet() - return net.IP(subnet.Broadcast()) - }() - - t.Run("Send", func(t *testing.T) { - var testCases []testCase - // Test every valid combination of bound/unbound, broadcast/multicast/unicast - // bound/destination address, and bound/not-bound to device. - for _, bindTo := range []net.IP{ - nil, // Do not bind. - net.IPv4zero, - net.IPv4bcast, - net.IPv4allsys, - subnetBcast, - dut.Net.RemoteIPv4, - dut.Net.RemoteIPv6, - } { - for _, sendTo := range []net.IP{ - net.IPv4bcast, - net.IPv4allsys, - subnetBcast, - dut.Net.LocalIPv4, - dut.Net.LocalIPv6, - } { - // Cannot send to an IPv4 address from a socket bound to IPv6 (except for IPv4-mapped IPv6), - // and viceversa. - if bindTo != nil && ((bindTo.To4() == nil) != (sendTo.To4() == nil)) { - continue - } - for _, bindToDevice := range []bool{true, false} { - expectData := true - switch { - case bindTo.Equal(dut.Net.RemoteIPv4): - // If we're explicitly bound to an interface's unicast address, - // packets are always sent on that interface. - case bindToDevice: - // If we're explicitly bound to an interface, packets are always - // sent on that interface. - case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast(): - // If we're not sending to limited broadcast or multicast, the route table - // will be consulted and packets will be sent on the correct interface. - default: - expectData = false - } - testCases = append( - testCases, - testCase{ - bindTo: bindTo, - sendTo: sendTo, - sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast), - bindToDevice: bindToDevice, - expectData: expectData, - }, - ) - } - } - } - for _, tc := range testCases { - boundTestCaseName := "unbound" - if tc.bindTo != nil { - boundTestCaseName = fmt.Sprintf("bindTo=%s", tc.bindTo) - } - t.Run(fmt.Sprintf("%s/sendTo=%s/bindToDevice=%t/expectData=%t", boundTestCaseName, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) { - runTestCase( - t, - dut, - tc, - func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) { - var destSockaddr unix.Sockaddr - if sendTo4 := tc.sendTo.To4(); sendTo4 != nil { - addr := unix.SockaddrInet4{ - Port: int(conn.SrcPort(t)), - } - copy(addr.Addr[:], sendTo4) - destSockaddr = &addr - } else { - addr := unix.SockaddrInet6{ - Port: int(conn.SrcPort(t)), - ZoneId: dut.Net.RemoteDevID, - } - copy(addr.Addr[:], tc.sendTo.To16()) - destSockaddr = &addr - } - if got, want := dut.SendTo(t, socketFD, payload, 0, destSockaddr), len(payload); int(got) != want { - t.Fatalf("got dut.SendTo = %d, want %d", got, want) - } - layers = append(layers, &testbench.Payload{ - Bytes: payload, - }) - _, err := conn.ExpectFrame(t, layers, time.Second) - - if !tc.expectData && err == nil { - t.Fatal("received unexpected packet, socket is not bound to device") - } - if err != nil && tc.expectData { - t.Fatal(err) - } - }, - ) - }) - } - }) - t.Run("Recv", func(t *testing.T) { - // Test every valid combination of broadcast/multicast/unicast - // bound/destination address, and bound/not-bound to device. - var testCases []testCase - for _, addr := range []net.IP{ - net.IPv4bcast, - net.IPv4allsys, - dut.Net.RemoteIPv4, - dut.Net.RemoteIPv6, - } { - for _, bindToDevice := range []bool{true, false} { - testCases = append( - testCases, - testCase{ - bindTo: addr, - sendTo: addr, - sendToBroadcast: addr.Equal(subnetBcast) || addr.Equal(net.IPv4bcast), - bindToDevice: bindToDevice, - expectData: true, - }, - ) - } - } - for _, bindTo := range []net.IP{ - net.IPv4zero, - subnetBcast, - dut.Net.RemoteIPv4, - } { - for _, sendTo := range []net.IP{ - subnetBcast, - net.IPv4bcast, - net.IPv4allsys, - } { - // TODO(gvisor.dev/issue/4896): Add bindTo=subnetBcast/sendTo=IPv4bcast - // and bindTo=subnetBcast/sendTo=IPv4allsys test cases. - if bindTo.Equal(subnetBcast) && (sendTo.Equal(net.IPv4bcast) || sendTo.IsMulticast()) { - continue - } - // Expect that a socket bound to a unicast address does not receive - // packets sent to an address other than the bound unicast address. - // - // Note: we cannot use net.IP.IsGlobalUnicast to test this condition - // because IsGlobalUnicast does not check whether the address is the - // subnet broadcast, and returns true in that case. - expectData := !bindTo.Equal(dut.Net.RemoteIPv4) || sendTo.Equal(dut.Net.RemoteIPv4) - for _, bindToDevice := range []bool{true, false} { - testCases = append( - testCases, - testCase{ - bindTo: bindTo, - sendTo: sendTo, - sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast), - bindToDevice: bindToDevice, - expectData: expectData, - }, - ) - } - } - } - for _, tc := range testCases { - t.Run(fmt.Sprintf("bindTo=%s/sendTo=%s/bindToDevice=%t/expectData=%t", tc.bindTo, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) { - runTestCase( - t, - dut, - tc, - func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) { - conn.SendFrame(t, layers, &testbench.Payload{Bytes: payload}) - - if tc.expectData { - got, want := dut.Recv(t, socketFD, int32(len(payload)+1), 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } - } else { - // Expected receive error, set a short receive timeout. - dut.SetSockOptTimeval( - t, - socketFD, - unix.SOL_SOCKET, - unix.SO_RCVTIMEO, - &unix.Timeval{ - Sec: 1, - Usec: 0, - }, - ) - ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, socketFD, 100, 0) - if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) - } - } - }, - ) - }) - } - }) -} - -func runTestCase( - t *testing.T, - dut testbench.DUT, - tc testCase, - runTc func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers), -) { - var ( - socketFD int32 - outgoingUDP, incomingUDP testbench.UDP - ) - if tc.bindTo != nil { - var remotePort uint16 - socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, tc.bindTo) - outgoingUDP.DstPort = &remotePort - incomingUDP.SrcPort = &remotePort - } else { - // An unbound socket will auto-bind to INNADDR_ANY and a random - // port on sendto. - socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) - } - defer dut.Close(t, socketFD) - if tc.bindToDevice { - dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) - } - - var ethernetLayer testbench.Ether - if tc.sendToBroadcast { - dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1) - - // When sending to broadcast (subnet or limited), the expected ethernet - // address is also broadcast. - ethernetBroadcastAddress := header.EthernetBroadcastAddress - ethernetLayer.DstAddr = ðernetBroadcastAddress - } else if tc.sendTo.IsMulticast() { - ethernetMulticastAddress := header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(tc.sendTo.To4())) - ethernetLayer.DstAddr = ðernetMulticastAddress - } - expectedLayers := testbench.Layers{ðernetLayer} - - var conn udpConn - if sendTo4 := tc.sendTo.To4(); sendTo4 != nil { - v4Conn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP) - conn = &v4Conn - expectedLayers = append( - expectedLayers, - &testbench.IPv4{ - DstAddr: testbench.Address(tcpip.Address(sendTo4)), - }, - ) - } else { - v6Conn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP) - conn = &v6Conn - expectedLayers = append( - expectedLayers, - &testbench.IPv6{ - DstAddr: testbench.Address(tcpip.Address(tc.sendTo)), - }, - ) - } - defer conn.Close(t) - - expectedLayers = append(expectedLayers, &incomingUDP) - for _, v := range []struct { - name string - payload []byte - }{ - {"emptypayload", nil}, - {"small payload", []byte("hello world")}, - {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)}, - // Even though UDP allows larger dgrams we don't test it here as - // they need to be fragmented and written out as individual - // frames. - } { - runTc(t, dut, conn, socketFD, tc, v.payload, expectedLayers) - } -} diff --git a/test/perf/BUILD b/test/perf/BUILD deleted file mode 100644 index ed899ac22..000000000 --- a/test/perf/BUILD +++ /dev/null @@ -1,138 +0,0 @@ -load("//test/runner:defs.bzl", "syscall_test") - -package(licenses = ["notice"]) - -syscall_test( - debug = False, - test = "//test/perf/linux:clock_getres_benchmark", -) - -syscall_test( - debug = False, - test = "//test/perf/linux:clock_gettime_benchmark", -) - -syscall_test( - debug = False, - test = "//test/perf/linux:death_benchmark", -) - -syscall_test( - debug = False, - test = "//test/perf/linux:epoll_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:fork_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:futex_benchmark", -) - -syscall_test( - size = "enormous", - debug = False, - tags = ["nogotsan"], - test = "//test/perf/linux:getdents_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:getpid_benchmark", -) - -syscall_test( - size = "enormous", - debug = False, - tags = ["nogotsan"], - test = "//test/perf/linux:gettid_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:mapping_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:open_benchmark", -) - -syscall_test( - debug = False, - test = "//test/perf/linux:pipe_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:randread_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:read_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:sched_yield_benchmark", -) - -syscall_test( - size = "large", - debug = False, - test = "//test/perf/linux:send_recv_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:seqwrite_benchmark", -) - -syscall_test( - size = "enormous", - debug = False, - test = "//test/perf/linux:signal_benchmark", -) - -syscall_test( - debug = False, - test = "//test/perf/linux:sleep_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:stat_benchmark", -) - -syscall_test( - size = "enormous", - add_overlay = True, - debug = False, - test = "//test/perf/linux:unlink_benchmark", -) - -syscall_test( - size = "large", - add_overlay = True, - debug = False, - test = "//test/perf/linux:write_benchmark", -) diff --git a/test/perf/linux/BUILD b/test/perf/linux/BUILD deleted file mode 100644 index dd1d2438c..000000000 --- a/test/perf/linux/BUILD +++ /dev/null @@ -1,372 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "gbenchmark", "gtest") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -cc_binary( - name = "getpid_benchmark", - testonly = 1, - srcs = [ - "getpid_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:test_main", - ], -) - -cc_binary( - name = "send_recv_benchmark", - testonly = 1, - srcs = [ - "send_recv_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/syscalls/linux:socket_test_util", - "//test/util:file_descriptor", - "//test/util:logging", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "@com_google_absl//absl/synchronization", - ], -) - -cc_binary( - name = "gettid_benchmark", - testonly = 1, - srcs = [ - "gettid_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:test_main", - ], -) - -cc_binary( - name = "sched_yield_benchmark", - testonly = 1, - srcs = [ - "sched_yield_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "clock_getres_benchmark", - testonly = 1, - srcs = [ - "clock_getres_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:test_main", - ], -) - -cc_binary( - name = "clock_gettime_benchmark", - testonly = 1, - srcs = [ - "clock_gettime_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:test_main", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "open_benchmark", - testonly = 1, - srcs = [ - "open_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - ], -) - -cc_binary( - name = "read_benchmark", - testonly = 1, - srcs = [ - "read_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "randread_benchmark", - testonly = 1, - srcs = [ - "randread_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:file_descriptor", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/random", - ], -) - -cc_binary( - name = "write_benchmark", - testonly = 1, - srcs = [ - "write_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "seqwrite_benchmark", - testonly = 1, - srcs = [ - "seqwrite_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/random", - ], -) - -cc_binary( - name = "pipe_benchmark", - testonly = 1, - srcs = [ - "pipe_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "fork_benchmark", - testonly = 1, - srcs = [ - "fork_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "@com_google_absl//absl/synchronization", - ], -) - -cc_binary( - name = "futex_benchmark", - testonly = 1, - srcs = [ - "futex_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:thread_util", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "epoll_benchmark", - testonly = 1, - srcs = [ - "epoll_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:epoll_util", - "//test/util:file_descriptor", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "death_benchmark", - testonly = 1, - srcs = [ - "death_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:test_main", - ], -) - -cc_binary( - name = "mapping_benchmark", - testonly = 1, - srcs = [ - "mapping_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "signal_benchmark", - testonly = 1, - srcs = [ - "signal_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "getdents_benchmark", - testonly = 1, - srcs = [ - "getdents_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:file_descriptor", - "//test/util:fs_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sleep_benchmark", - testonly = 1, - srcs = [ - "sleep_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:logging", - "//test/util:test_main", - ], -) - -cc_binary( - name = "stat_benchmark", - testonly = 1, - srcs = [ - "stat_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:fs_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/strings", - ], -) - -cc_binary( - name = "unlink_benchmark", - testonly = 1, - srcs = [ - "unlink_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:fs_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "open_read_close_benchmark", - testonly = 1, - srcs = [ - "open_read_close_benchmark.cc", - ], - deps = [ - gbenchmark, - gtest, - "//test/util:fs_util", - "//test/util:logging", - "//test/util:temp_path", - "//test/util:test_main", - ], -) diff --git a/test/perf/linux/clock_getres_benchmark.cc b/test/perf/linux/clock_getres_benchmark.cc deleted file mode 100644 index b051293ad..000000000 --- a/test/perf/linux/clock_getres_benchmark.cc +++ /dev/null @@ -1,39 +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. - -#include <time.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" - -namespace gvisor { -namespace testing { - -namespace { - -// clock_getres(1) is very nearly a no-op syscall, but it does require copying -// out to a userspace struct. It thus provides a nice small copy-out benchmark. -void BM_ClockGetRes(benchmark::State& state) { - struct timespec ts; - for (auto _ : state) { - clock_getres(CLOCK_MONOTONIC, &ts); - } -} - -BENCHMARK(BM_ClockGetRes); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/clock_gettime_benchmark.cc b/test/perf/linux/clock_gettime_benchmark.cc deleted file mode 100644 index 6691bebd9..000000000 --- a/test/perf/linux/clock_gettime_benchmark.cc +++ /dev/null @@ -1,60 +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. - -#include <pthread.h> -#include <time.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "benchmark/benchmark.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_ClockGettimeThreadCPUTime(benchmark::State& state) { - clockid_t clockid; - ASSERT_EQ(0, pthread_getcpuclockid(pthread_self(), &clockid)); - struct timespec tp; - - for (auto _ : state) { - clock_gettime(clockid, &tp); - } -} - -BENCHMARK(BM_ClockGettimeThreadCPUTime); - -void BM_VDSOClockGettime(benchmark::State& state) { - const clockid_t clock = state.range(0); - struct timespec tp; - absl::Time start = absl::Now(); - - // Don't benchmark the calibration phase. - while (absl::Now() < start + absl::Milliseconds(2100)) { - clock_gettime(clock, &tp); - } - - for (auto _ : state) { - clock_gettime(clock, &tp); - } -} - -BENCHMARK(BM_VDSOClockGettime)->Arg(CLOCK_MONOTONIC)->Arg(CLOCK_REALTIME); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/death_benchmark.cc b/test/perf/linux/death_benchmark.cc deleted file mode 100644 index cb2b6fd07..000000000 --- a/test/perf/linux/death_benchmark.cc +++ /dev/null @@ -1,36 +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. - -#include <signal.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" - -namespace gvisor { -namespace testing { - -namespace { - -// DeathTest is not so much a microbenchmark as a macrobenchmark. It is testing -// the ability of gVisor (on whatever platform) to execute all the related -// stack-dumping routines associated with EXPECT_EXIT / EXPECT_DEATH. -TEST(DeathTest, ZeroEqualsOne) { - EXPECT_EXIT({ TEST_CHECK(0 == 1); }, ::testing::KilledBySignal(SIGABRT), ""); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/epoll_benchmark.cc b/test/perf/linux/epoll_benchmark.cc deleted file mode 100644 index 0b121338a..000000000 --- a/test/perf/linux/epoll_benchmark.cc +++ /dev/null @@ -1,99 +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. - -#include <sys/epoll.h> -#include <sys/eventfd.h> - -#include <atomic> -#include <cerrno> -#include <cstdint> -#include <cstdlib> -#include <ctime> -#include <memory> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "benchmark/benchmark.h" -#include "test/util/epoll_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Returns a new eventfd. -PosixErrorOr<FileDescriptor> NewEventFD() { - int fd = eventfd(0, /* flags = */ 0); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, "eventfd"); - } - return FileDescriptor(fd); -} - -// Also stolen from epoll.cc unit tests. -void BM_EpollTimeout(benchmark::State& state) { - constexpr int kFDsPerEpoll = 3; - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, 0)); - } - - struct epoll_event result[kFDsPerEpoll]; - int timeout_ms = state.range(0); - - for (auto _ : state) { - EXPECT_EQ(0, epoll_wait(epollfd.get(), result, kFDsPerEpoll, timeout_ms)); - } -} - -BENCHMARK(BM_EpollTimeout)->Range(0, 8); - -// Also stolen from epoll.cc unit tests. -void BM_EpollAllEvents(benchmark::State& state) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - const int fds_per_epoll = state.range(0); - constexpr uint64_t kEventVal = 5; - - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < fds_per_epoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, 0)); - - ASSERT_THAT(WriteFd(eventfds[i].get(), &kEventVal, sizeof(kEventVal)), - SyscallSucceedsWithValue(sizeof(kEventVal))); - } - - std::vector<struct epoll_event> result(fds_per_epoll); - - for (auto _ : state) { - EXPECT_EQ(fds_per_epoll, - epoll_wait(epollfd.get(), result.data(), fds_per_epoll, 0)); - } -} - -BENCHMARK(BM_EpollAllEvents)->Range(2, 1024); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/fork_benchmark.cc b/test/perf/linux/fork_benchmark.cc deleted file mode 100644 index 84fdbc8a0..000000000 --- a/test/perf/linux/fork_benchmark.cc +++ /dev/null @@ -1,350 +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. - -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/synchronization/barrier.h" -#include "benchmark/benchmark.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kBusyMax = 250; - -// Do some CPU-bound busy-work. -int busy(int max) { - // Prevent the compiler from optimizing this work away, - volatile int count = 0; - - for (int i = 1; i < max; i++) { - for (int j = 2; j < i / 2; j++) { - if (i % j == 0) { - count++; - } - } - } - - return count; -} - -void BM_CPUBoundUniprocess(benchmark::State& state) { - for (auto _ : state) { - busy(kBusyMax); - } -} - -BENCHMARK(BM_CPUBoundUniprocess); - -void BM_CPUBoundAsymmetric(benchmark::State& state) { - const size_t max = state.max_iterations; - pid_t child = fork(); - if (child == 0) { - for (int i = 0; i < max; i++) { - busy(kBusyMax); - } - _exit(0); - } - ASSERT_THAT(child, SyscallSucceeds()); - ASSERT_TRUE(state.KeepRunningBatch(max)); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); - ASSERT_FALSE(state.KeepRunning()); -} - -BENCHMARK(BM_CPUBoundAsymmetric)->UseRealTime(); - -void BM_CPUBoundSymmetric(benchmark::State& state) { - std::vector<pid_t> children; - auto child_cleanup = Cleanup([&] { - for (const pid_t child : children) { - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); - } - ASSERT_FALSE(state.KeepRunning()); - }); - - const int processes = state.range(0); - for (int i = 0; i < processes; i++) { - size_t cur = (state.max_iterations + (processes - 1)) / processes; - if ((state.iterations() + cur) >= state.max_iterations) { - cur = state.max_iterations - state.iterations(); - } - pid_t child = fork(); - if (child == 0) { - for (int i = 0; i < cur; i++) { - busy(kBusyMax); - } - _exit(0); - } - ASSERT_THAT(child, SyscallSucceeds()); - if (cur > 0) { - // We can have a zero cur here, depending. - ASSERT_TRUE(state.KeepRunningBatch(cur)); - } - children.push_back(child); - } -} - -BENCHMARK(BM_CPUBoundSymmetric)->Range(2, 16)->UseRealTime(); - -// Child routine for ProcessSwitch/ThreadSwitch. -// Reads from readfd and writes the result to writefd. -void SwitchChild(int readfd, int writefd) { - while (1) { - char buf; - int ret = ReadFd(readfd, &buf, 1); - if (ret == 0) { - break; - } - TEST_CHECK_MSG(ret == 1, "read failed"); - - ret = WriteFd(writefd, &buf, 1); - if (ret == -1) { - TEST_CHECK_MSG(errno == EPIPE, "unexpected write failure"); - break; - } - TEST_CHECK_MSG(ret == 1, "write failed"); - } -} - -// Send bytes in a loop through a series of pipes, each passing through a -// different process. -// -// Proc 0 Proc 1 -// * ----------> * -// ^ Pipe 1 | -// | | -// | Pipe 0 | Pipe 2 -// | | -// | | -// | Pipe 3 v -// * <---------- * -// Proc 3 Proc 2 -// -// This exercises context switching through multiple processes. -void BM_ProcessSwitch(benchmark::State& state) { - // Code below assumes there are at least two processes. - const int num_processes = state.range(0); - ASSERT_GE(num_processes, 2); - - std::vector<pid_t> children; - auto child_cleanup = Cleanup([&] { - for (const pid_t child : children) { - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); - } - }); - - // Must come after children, as the FDs must be closed before the children - // will exit. - std::vector<FileDescriptor> read_fds; - std::vector<FileDescriptor> write_fds; - - for (int i = 0; i < num_processes; i++) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - read_fds.emplace_back(fds[0]); - write_fds.emplace_back(fds[1]); - } - - // This process is one of the processes in the loop. It will be considered - // index 0. - for (int i = 1; i < num_processes; i++) { - // Read from current pipe index, write to next. - const int read_index = i; - const int read_fd = read_fds[read_index].get(); - - const int write_index = (i + 1) % num_processes; - const int write_fd = write_fds[write_index].get(); - - // std::vector isn't safe to use from the fork child. - FileDescriptor* read_array = read_fds.data(); - FileDescriptor* write_array = write_fds.data(); - - pid_t child = fork(); - if (!child) { - // Close all other FDs. - for (int j = 0; j < num_processes; j++) { - if (j != read_index) { - read_array[j].reset(); - } - if (j != write_index) { - write_array[j].reset(); - } - } - - SwitchChild(read_fd, write_fd); - _exit(0); - } - ASSERT_THAT(child, SyscallSucceeds()); - children.push_back(child); - } - - // Read from current pipe index (0), write to next (1). - const int read_index = 0; - const int read_fd = read_fds[read_index].get(); - - const int write_index = 1; - const int write_fd = write_fds[write_index].get(); - - // Kick start the loop. - char buf = 'a'; - ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1)); - - for (auto _ : state) { - ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1)); - ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1)); - } -} - -BENCHMARK(BM_ProcessSwitch)->Range(2, 16)->UseRealTime(); - -// Equivalent to BM_ThreadSwitch using threads instead of processes. -void BM_ThreadSwitch(benchmark::State& state) { - // Code below assumes there are at least two threads. - const int num_threads = state.range(0); - ASSERT_GE(num_threads, 2); - - // Must come after threads, as the FDs must be closed before the children - // will exit. - std::vector<std::unique_ptr<ScopedThread>> threads; - std::vector<FileDescriptor> read_fds; - std::vector<FileDescriptor> write_fds; - - for (int i = 0; i < num_threads; i++) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - read_fds.emplace_back(fds[0]); - write_fds.emplace_back(fds[1]); - } - - // This thread is one of the threads in the loop. It will be considered - // index 0. - for (int i = 1; i < num_threads; i++) { - // Read from current pipe index, write to next. - // - // Transfer ownership of the FDs to the thread. - const int read_index = i; - const int read_fd = read_fds[read_index].release(); - - const int write_index = (i + 1) % num_threads; - const int write_fd = write_fds[write_index].release(); - - threads.emplace_back(std::make_unique<ScopedThread>([read_fd, write_fd] { - FileDescriptor read(read_fd); - FileDescriptor write(write_fd); - SwitchChild(read.get(), write.get()); - })); - } - - // Read from current pipe index (0), write to next (1). - const int read_index = 0; - const int read_fd = read_fds[read_index].get(); - - const int write_index = 1; - const int write_fd = write_fds[write_index].get(); - - // Kick start the loop. - char buf = 'a'; - ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1)); - - for (auto _ : state) { - ASSERT_THAT(ReadFd(read_fd, &buf, 1), SyscallSucceedsWithValue(1)); - ASSERT_THAT(WriteFd(write_fd, &buf, 1), SyscallSucceedsWithValue(1)); - } - - // The two FDs still owned by this thread are closed, causing the next thread - // to exit its loop and close its FDs, and so on until all threads exit. -} - -BENCHMARK(BM_ThreadSwitch)->Range(2, 16)->UseRealTime(); - -void BM_ThreadStart(benchmark::State& state) { - const int num_threads = state.range(0); - - for (auto _ : state) { - state.PauseTiming(); - - auto barrier = new absl::Barrier(num_threads + 1); - std::vector<std::unique_ptr<ScopedThread>> threads; - - state.ResumeTiming(); - - for (size_t i = 0; i < num_threads; ++i) { - threads.emplace_back(std::make_unique<ScopedThread>([barrier] { - if (barrier->Block()) { - delete barrier; - } - })); - } - - if (barrier->Block()) { - delete barrier; - } - - state.PauseTiming(); - - for (const auto& thread : threads) { - thread->Join(); - } - - state.ResumeTiming(); - } -} - -BENCHMARK(BM_ThreadStart)->Range(1, 2048)->UseRealTime(); - -// Benchmark the complete fork + exit + wait. -void BM_ProcessLifecycle(benchmark::State& state) { - const int num_procs = state.range(0); - - std::vector<pid_t> pids(num_procs); - for (auto _ : state) { - for (size_t i = 0; i < num_procs; ++i) { - int pid = fork(); - if (pid == 0) { - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - pids[i] = pid; - } - - for (const int pid : pids) { - ASSERT_THAT(RetryEINTR(waitpid)(pid, nullptr, 0), - SyscallSucceedsWithValue(pid)); - } - } -} - -BENCHMARK(BM_ProcessLifecycle)->Range(1, 512)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/futex_benchmark.cc b/test/perf/linux/futex_benchmark.cc deleted file mode 100644 index e686041c9..000000000 --- a/test/perf/linux/futex_benchmark.cc +++ /dev/null @@ -1,198 +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. - -#include <linux/futex.h> - -#include <atomic> -#include <cerrno> -#include <cstdint> -#include <cstdlib> -#include <ctime> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -inline int FutexWait(std::atomic<int32_t>* v, int32_t val) { - return syscall(SYS_futex, v, FUTEX_WAIT_PRIVATE, val, nullptr); -} - -inline int FutexWaitMonotonicTimeout(std::atomic<int32_t>* v, int32_t val, - const struct timespec* timeout) { - return syscall(SYS_futex, v, FUTEX_WAIT_PRIVATE, val, timeout); -} - -inline int FutexWaitMonotonicDeadline(std::atomic<int32_t>* v, int32_t val, - const struct timespec* deadline) { - return syscall(SYS_futex, v, FUTEX_WAIT_BITSET_PRIVATE, val, deadline, - nullptr, FUTEX_BITSET_MATCH_ANY); -} - -inline int FutexWaitRealtimeDeadline(std::atomic<int32_t>* v, int32_t val, - const struct timespec* deadline) { - return syscall(SYS_futex, v, FUTEX_WAIT_BITSET_PRIVATE | FUTEX_CLOCK_REALTIME, - val, deadline, nullptr, FUTEX_BITSET_MATCH_ANY); -} - -inline int FutexWake(std::atomic<int32_t>* v, int32_t count) { - return syscall(SYS_futex, v, FUTEX_WAKE_PRIVATE, count); -} - -// This just uses FUTEX_WAKE on an address with nothing waiting, very simple. -void BM_FutexWakeNop(benchmark::State& state) { - std::atomic<int32_t> v(0); - - for (auto _ : state) { - TEST_PCHECK(FutexWake(&v, 1) == 0); - } -} - -BENCHMARK(BM_FutexWakeNop)->MinTime(5); - -// This just uses FUTEX_WAIT on an address whose value has changed, i.e., the -// syscall won't wait. -void BM_FutexWaitNop(benchmark::State& state) { - std::atomic<int32_t> v(0); - - for (auto _ : state) { - TEST_PCHECK(FutexWait(&v, 1) == -1 && errno == EAGAIN); - } -} - -BENCHMARK(BM_FutexWaitNop)->MinTime(5); - -// This uses FUTEX_WAIT with a timeout on an address whose value never -// changes, such that it always times out. Timeout overhead can be estimated by -// timer overruns for short timeouts. -void BM_FutexWaitMonotonicTimeout(benchmark::State& state) { - const absl::Duration timeout = absl::Nanoseconds(state.range(0)); - std::atomic<int32_t> v(0); - auto ts = absl::ToTimespec(timeout); - - for (auto _ : state) { - TEST_PCHECK(FutexWaitMonotonicTimeout(&v, 0, &ts) == -1 && - errno == ETIMEDOUT); - } -} - -BENCHMARK(BM_FutexWaitMonotonicTimeout) - ->MinTime(5) - ->UseRealTime() - ->Arg(1) - ->Arg(10) - ->Arg(100) - ->Arg(1000) - ->Arg(10000); - -// This uses FUTEX_WAIT_BITSET with a deadline that is in the past. This allows -// estimation of the overhead of setting up a timer for a deadline (as opposed -// to a timeout as specified for FUTEX_WAIT). -void BM_FutexWaitMonotonicDeadline(benchmark::State& state) { - std::atomic<int32_t> v(0); - struct timespec ts = {}; - - for (auto _ : state) { - TEST_PCHECK(FutexWaitMonotonicDeadline(&v, 0, &ts) == -1 && - errno == ETIMEDOUT); - } -} - -BENCHMARK(BM_FutexWaitMonotonicDeadline)->MinTime(5); - -// This is equivalent to BM_FutexWaitMonotonicDeadline, but uses CLOCK_REALTIME -// instead of CLOCK_MONOTONIC for the deadline. -void BM_FutexWaitRealtimeDeadline(benchmark::State& state) { - std::atomic<int32_t> v(0); - struct timespec ts = {}; - - for (auto _ : state) { - TEST_PCHECK(FutexWaitRealtimeDeadline(&v, 0, &ts) == -1 && - errno == ETIMEDOUT); - } -} - -BENCHMARK(BM_FutexWaitRealtimeDeadline)->MinTime(5); - -int64_t GetCurrentMonotonicTimeNanos() { - struct timespec ts; - TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) != -1); - return ts.tv_sec * 1000000000ULL + ts.tv_nsec; -} - -void SpinNanos(int64_t delay_ns) { - if (delay_ns <= 0) { - return; - } - const int64_t end = GetCurrentMonotonicTimeNanos() + delay_ns; - while (GetCurrentMonotonicTimeNanos() < end) { - // spin - } -} - -// Each iteration of FutexRoundtripDelayed involves a thread sending a futex -// wakeup to another thread, which spins for delay_us and then sends a futex -// wakeup back. The time per iteration is 2 * (delay_us + kBeforeWakeDelayNs + -// futex/scheduling overhead). -void BM_FutexRoundtripDelayed(benchmark::State& state) { - const int delay_us = state.range(0); - const int64_t delay_ns = delay_us * 1000; - // Spin for an extra kBeforeWakeDelayNs before invoking FUTEX_WAKE to reduce - // the probability that the wakeup comes before the wait, preventing the wait - // from ever taking effect and causing the benchmark to underestimate the - // actual wakeup time. - constexpr int64_t kBeforeWakeDelayNs = 500; - std::atomic<int32_t> v(0); - ScopedThread t([&] { - for (int i = 0; i < state.max_iterations; i++) { - SpinNanos(delay_ns); - while (v.load(std::memory_order_acquire) == 0) { - FutexWait(&v, 0); - } - SpinNanos(kBeforeWakeDelayNs + delay_ns); - v.store(0, std::memory_order_release); - FutexWake(&v, 1); - } - }); - for (auto _ : state) { - SpinNanos(kBeforeWakeDelayNs + delay_ns); - v.store(1, std::memory_order_release); - FutexWake(&v, 1); - SpinNanos(delay_ns); - while (v.load(std::memory_order_acquire) == 1) { - FutexWait(&v, 1); - } - } -} - -BENCHMARK(BM_FutexRoundtripDelayed) - ->MinTime(5) - ->UseRealTime() - ->Arg(0) - ->Arg(10) - ->Arg(20) - ->Arg(50) - ->Arg(100); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/getdents_benchmark.cc b/test/perf/linux/getdents_benchmark.cc deleted file mode 100644 index 9030eb356..000000000 --- a/test/perf/linux/getdents_benchmark.cc +++ /dev/null @@ -1,149 +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. - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -#ifndef SYS_getdents64 -#if defined(__x86_64__) -#define SYS_getdents64 217 -#elif defined(__aarch64__) -#define SYS_getdents64 217 -#else -#error "Unknown architecture" -#endif -#endif // SYS_getdents64 - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kBufferSize = 65536; - -PosixErrorOr<TempPath> CreateDirectory(int count, - std::vector<std::string>* files) { - ASSIGN_OR_RETURN_ERRNO(TempPath dir, TempPath::CreateDir()); - - ASSIGN_OR_RETURN_ERRNO(FileDescriptor dfd, - Open(dir.path(), O_RDONLY | O_DIRECTORY)); - - for (int i = 0; i < count; i++) { - auto file = NewTempRelPath(); - auto res = MknodAt(dfd, file, S_IFREG | 0644, 0); - RETURN_IF_ERRNO(res); - files->push_back(file); - } - - return std::move(dir); -} - -PosixError CleanupDirectory(const TempPath& dir, - std::vector<std::string>* files) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor dfd, - Open(dir.path(), O_RDONLY | O_DIRECTORY)); - - for (auto it = files->begin(); it != files->end(); ++it) { - auto res = UnlinkAt(dfd, *it, 0); - RETURN_IF_ERRNO(res); - } - return NoError(); -} - -// Creates a directory containing `files` files, and reads all the directory -// entries from the directory using a single FD. -void BM_GetdentsSameFD(benchmark::State& state) { - // Create directory with given files. - const int count = state.range(0); - - // Keep a vector of all of the file TempPaths that is destroyed before dir. - // - // Normally, we'd simply allow dir to recursively clean up the contained - // files, but that recursive cleanup uses getdents, which may be very slow in - // extreme benchmarks. - TempPath dir; - std::vector<std::string> files; - dir = ASSERT_NO_ERRNO_AND_VALUE(CreateDirectory(count, &files)); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY)); - char buffer[kBufferSize]; - - // We read all directory entries on each iteration, but report this as a - // "batch" iteration so that reported times are per file. - while (state.KeepRunningBatch(count)) { - ASSERT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds()); - - int ret; - do { - ASSERT_THAT(ret = syscall(SYS_getdents64, fd.get(), buffer, kBufferSize), - SyscallSucceeds()); - } while (ret > 0); - } - - ASSERT_NO_ERRNO(CleanupDirectory(dir, &files)); - - state.SetItemsProcessed(state.iterations()); -} - -BENCHMARK(BM_GetdentsSameFD)->Range(1, 1 << 12)->UseRealTime(); - -// Creates a directory containing `files` files, and reads all the directory -// entries from the directory using a new FD each time. -void BM_GetdentsNewFD(benchmark::State& state) { - // Create directory with given files. - const int count = state.range(0); - - // Keep a vector of all of the file TempPaths that is destroyed before dir. - // - // Normally, we'd simply allow dir to recursively clean up the contained - // files, but that recursive cleanup uses getdents, which may be very slow in - // extreme benchmarks. - TempPath dir; - std::vector<std::string> files; - dir = ASSERT_NO_ERRNO_AND_VALUE(CreateDirectory(count, &files)); - char buffer[kBufferSize]; - - // We read all directory entries on each iteration, but report this as a - // "batch" iteration so that reported times are per file. - while (state.KeepRunningBatch(count)) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY)); - - int ret; - do { - ASSERT_THAT(ret = syscall(SYS_getdents64, fd.get(), buffer, kBufferSize), - SyscallSucceeds()); - } while (ret > 0); - } - - ASSERT_NO_ERRNO(CleanupDirectory(dir, &files)); - - state.SetItemsProcessed(state.iterations()); -} - -BENCHMARK(BM_GetdentsNewFD)->Range(1, 1 << 12)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/getpid_benchmark.cc b/test/perf/linux/getpid_benchmark.cc deleted file mode 100644 index db74cb264..000000000 --- a/test/perf/linux/getpid_benchmark.cc +++ /dev/null @@ -1,37 +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. - -#include <sys/syscall.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Getpid(benchmark::State& state) { - for (auto _ : state) { - syscall(SYS_getpid); - } -} - -BENCHMARK(BM_Getpid); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/gettid_benchmark.cc b/test/perf/linux/gettid_benchmark.cc deleted file mode 100644 index 8f4961f5e..000000000 --- a/test/perf/linux/gettid_benchmark.cc +++ /dev/null @@ -1,38 +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. - -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Gettid(benchmark::State& state) { - for (auto _ : state) { - syscall(SYS_gettid); - } -} - -BENCHMARK(BM_Gettid)->ThreadRange(1, 4000)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/mapping_benchmark.cc b/test/perf/linux/mapping_benchmark.cc deleted file mode 100644 index 39c30fe69..000000000 --- a/test/perf/linux/mapping_benchmark.cc +++ /dev/null @@ -1,163 +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. - -#include <stdlib.h> -#include <sys/mman.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Conservative value for /proc/sys/vm/max_map_count, which limits the number of -// VMAs, minus a safety margin for VMAs that already exist for the test binary. -// The default value for max_map_count is -// include/linux/mm.h:DEFAULT_MAX_MAP_COUNT = 65530. -constexpr size_t kMaxVMAs = 64001; - -// Map then unmap pages without touching them. -void BM_MapUnmap(benchmark::State& state) { - // Number of pages to map. - const int pages = state.range(0); - - while (state.KeepRunning()) { - void* addr = mmap(0, pages * kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed"); - - int ret = munmap(addr, pages * kPageSize); - TEST_CHECK_MSG(ret == 0, "munmap failed"); - } -} - -BENCHMARK(BM_MapUnmap)->Range(1, 1 << 17)->UseRealTime(); - -// Map, touch, then unmap pages. -void BM_MapTouchUnmap(benchmark::State& state) { - // Number of pages to map. - const int pages = state.range(0); - - while (state.KeepRunning()) { - void* addr = mmap(0, pages * kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed"); - - char* c = reinterpret_cast<char*>(addr); - char* end = c + pages * kPageSize; - while (c < end) { - *c = 42; - c += kPageSize; - } - - int ret = munmap(addr, pages * kPageSize); - TEST_CHECK_MSG(ret == 0, "munmap failed"); - } -} - -BENCHMARK(BM_MapTouchUnmap)->Range(1, 1 << 17)->UseRealTime(); - -// Map and touch many pages, unmapping all at once. -// -// NOTE(b/111429208): This is a regression test to ensure performant mapping and -// allocation even with tons of mappings. -void BM_MapTouchMany(benchmark::State& state) { - // Number of pages to map. - const int page_count = state.range(0); - - while (state.KeepRunning()) { - std::vector<void*> pages; - - for (int i = 0; i < page_count; i++) { - void* addr = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - TEST_CHECK_MSG(addr != MAP_FAILED, "mmap failed"); - - char* c = reinterpret_cast<char*>(addr); - *c = 42; - - pages.push_back(addr); - } - - for (void* addr : pages) { - int ret = munmap(addr, kPageSize); - TEST_CHECK_MSG(ret == 0, "munmap failed"); - } - } - - state.SetBytesProcessed(kPageSize * page_count * state.iterations()); -} - -BENCHMARK(BM_MapTouchMany)->Range(1, 1 << 12)->UseRealTime(); - -void BM_PageFault(benchmark::State& state) { - // Map the region in which we will take page faults. To ensure that each page - // fault maps only a single page, each page we touch must correspond to a - // distinct VMA. Thus we need a 1-page gap between each 1-page VMA. However, - // each gap consists of a PROT_NONE VMA, instead of an unmapped hole, so that - // if there are background threads running, they can't inadvertently creating - // mappings in our gaps that are unmapped when the test ends. - size_t test_pages = kMaxVMAs; - // Ensure that test_pages is odd, since we want the test region to both - // begin and end with a mapped page. - if (test_pages % 2 == 0) { - test_pages--; - } - const size_t test_region_bytes = test_pages * kPageSize; - // Use MAP_SHARED here because madvise(MADV_DONTNEED) on private mappings on - // gVisor won't force future sentry page faults (by design). Use MAP_POPULATE - // so that Linux pre-allocates the shmem file used to back the mapping. - Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(test_region_bytes, PROT_READ, MAP_SHARED | MAP_POPULATE)); - for (size_t i = 0; i < test_pages / 2; i++) { - ASSERT_THAT( - mprotect(reinterpret_cast<void*>(m.addr() + ((2 * i + 1) * kPageSize)), - kPageSize, PROT_NONE), - SyscallSucceeds()); - } - - const size_t mapped_pages = test_pages / 2 + 1; - // "Start" at the end of the mapped region to force the mapped region to be - // reset, since we mapped it with MAP_POPULATE. - size_t cur_page = mapped_pages; - for (auto _ : state) { - if (cur_page >= mapped_pages) { - // We've reached the end of our mapped region and have to reset it to - // incur page faults again. - state.PauseTiming(); - ASSERT_THAT(madvise(m.ptr(), test_region_bytes, MADV_DONTNEED), - SyscallSucceeds()); - cur_page = 0; - state.ResumeTiming(); - } - const uintptr_t addr = m.addr() + (2 * cur_page * kPageSize); - const char c = *reinterpret_cast<volatile char*>(addr); - benchmark::DoNotOptimize(c); - cur_page++; - } -} - -BENCHMARK(BM_PageFault)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/open_benchmark.cc b/test/perf/linux/open_benchmark.cc deleted file mode 100644 index 68008f6d5..000000000 --- a/test/perf/linux/open_benchmark.cc +++ /dev/null @@ -1,56 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <unistd.h> - -#include <memory> -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Open(benchmark::State& state) { - const int size = state.range(0); - std::vector<TempPath> cache; - for (int i = 0; i < size; i++) { - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - cache.emplace_back(std::move(path)); - } - - unsigned int seed = 1; - for (auto _ : state) { - const int chosen = rand_r(&seed) % size; - int fd = open(cache[chosen].path().c_str(), O_RDONLY); - TEST_CHECK(fd != -1); - close(fd); - } -} - -BENCHMARK(BM_Open)->Range(1, 128)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/open_read_close_benchmark.cc b/test/perf/linux/open_read_close_benchmark.cc deleted file mode 100644 index 8b023a3d8..000000000 --- a/test/perf/linux/open_read_close_benchmark.cc +++ /dev/null @@ -1,61 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <unistd.h> - -#include <memory> -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_OpenReadClose(benchmark::State& state) { - const int size = state.range(0); - std::vector<TempPath> cache; - for (int i = 0; i < size; i++) { - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "some content", 0644)); - cache.emplace_back(std::move(path)); - } - - char buf[1]; - unsigned int seed = 1; - for (auto _ : state) { - const int chosen = rand_r(&seed) % size; - int fd = open(cache[chosen].path().c_str(), O_RDONLY); - TEST_CHECK(fd != -1); - TEST_CHECK(read(fd, buf, 1) == 1); - close(fd); - } -} - -// Gofer dentry cache is 1000 by default. Go over it to force files to be closed -// for real. -BENCHMARK(BM_OpenReadClose)->Range(1000, 16384)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/pipe_benchmark.cc b/test/perf/linux/pipe_benchmark.cc deleted file mode 100644 index 8f5f6a2a3..000000000 --- a/test/perf/linux/pipe_benchmark.cc +++ /dev/null @@ -1,66 +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. - -#include <stdlib.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <cerrno> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Pipe(benchmark::State& state) { - int fds[2]; - TEST_CHECK(pipe(fds) == 0); - - const int size = state.range(0); - std::vector<char> wbuf(size); - std::vector<char> rbuf(size); - RandomizeBuffer(wbuf.data(), size); - - ScopedThread t([&] { - auto const fd = fds[1]; - for (int i = 0; i < state.max_iterations; i++) { - TEST_CHECK(WriteFd(fd, wbuf.data(), wbuf.size()) == size); - } - }); - - for (auto _ : state) { - TEST_CHECK(ReadFd(fds[0], rbuf.data(), rbuf.size()) == size); - } - - t.Join(); - - close(fds[0]); - close(fds[1]); - - state.SetBytesProcessed(static_cast<int64_t>(size) * - static_cast<int64_t>(state.iterations())); -} - -BENCHMARK(BM_Pipe)->Range(1, 1 << 20)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/randread_benchmark.cc b/test/perf/linux/randread_benchmark.cc deleted file mode 100644 index b0eb8c24e..000000000 --- a/test/perf/linux/randread_benchmark.cc +++ /dev/null @@ -1,100 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/uio.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Create a 1GB file that will be read from at random positions. This should -// invalid any performance gains from caching. -const uint64_t kFileSize = 1ULL << 30; - -// How many bytes to write at once to initialize the file used to read from. -const uint32_t kWriteSize = 65536; - -// Largest benchmarked read unit. -const uint32_t kMaxRead = 1UL << 26; - -TempPath CreateFile(uint64_t file_size) { - auto path = TempPath::CreateFile().ValueOrDie(); - FileDescriptor fd = Open(path.path(), O_WRONLY).ValueOrDie(); - - // Try to minimize syscalls by using maximum size writev() requests. - std::vector<char> buffer(kWriteSize); - RandomizeBuffer(buffer.data(), buffer.size()); - const std::vector<std::vector<struct iovec>> iovecs_list = - GenerateIovecs(file_size, buffer.data(), buffer.size()); - for (const auto& iovecs : iovecs_list) { - TEST_CHECK(writev(fd.get(), iovecs.data(), iovecs.size()) >= 0); - } - - return path; -} - -// Global test state, initialized once per process lifetime. -struct GlobalState { - const TempPath tmpfile; - explicit GlobalState(TempPath tfile) : tmpfile(std::move(tfile)) {} -}; - -GlobalState& GetGlobalState() { - // This gets created only once throughout the lifetime of the process. - // Use a dynamically allocated object (that is never deleted) to avoid order - // of destruction of static storage variables issues. - static GlobalState* const state = - // The actual file size is the maximum random seek range (kFileSize) + the - // maximum read size so we can read that number of bytes at the end of the - // file. - new GlobalState(CreateFile(kFileSize + kMaxRead)); - return *state; -} - -void BM_RandRead(benchmark::State& state) { - const int size = state.range(0); - - GlobalState& global_state = GetGlobalState(); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(global_state.tmpfile.path(), O_RDONLY)); - std::vector<char> buf(size); - - unsigned int seed = 1; - for (auto _ : state) { - TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), - rand_r(&seed) % kFileSize) == size); - } - - state.SetBytesProcessed(static_cast<int64_t>(size) * - static_cast<int64_t>(state.iterations())); -} - -BENCHMARK(BM_RandRead)->Range(1, kMaxRead)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/read_benchmark.cc b/test/perf/linux/read_benchmark.cc deleted file mode 100644 index 62445867d..000000000 --- a/test/perf/linux/read_benchmark.cc +++ /dev/null @@ -1,53 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Read(benchmark::State& state) { - const int size = state.range(0); - const std::string contents(size, 0); - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), contents, TempPath::kDefaultFileMode)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDONLY)); - - std::vector<char> buf(size); - for (auto _ : state) { - TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), 0) == size); - } - - state.SetBytesProcessed(static_cast<int64_t>(size) * - static_cast<int64_t>(state.iterations())); -} - -BENCHMARK(BM_Read)->Range(1, 1 << 26)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/sched_yield_benchmark.cc b/test/perf/linux/sched_yield_benchmark.cc deleted file mode 100644 index 6756b5575..000000000 --- a/test/perf/linux/sched_yield_benchmark.cc +++ /dev/null @@ -1,37 +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. - -#include <sched.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Sched_yield(benchmark::State& state) { - for (auto ignored : state) { - TEST_CHECK(sched_yield() == 0); - } -} - -BENCHMARK(BM_Sched_yield)->ThreadRange(1, 2000)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/send_recv_benchmark.cc b/test/perf/linux/send_recv_benchmark.cc deleted file mode 100644 index d73e49523..000000000 --- a/test/perf/linux/send_recv_benchmark.cc +++ /dev/null @@ -1,372 +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. - -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> - -#include <cstring> - -#include "gtest/gtest.h" -#include "absl/synchronization/notification.h" -#include "benchmark/benchmark.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr ssize_t kMessageSize = 1024; - -class Message { - public: - explicit Message(int byte = 0) : Message(byte, kMessageSize, 0) {} - - explicit Message(int byte, int sz) : Message(byte, sz, 0) {} - - explicit Message(int byte, int sz, int cmsg_sz) - : buffer_(sz, byte), cmsg_buffer_(cmsg_sz, 0) { - iov_.iov_base = buffer_.data(); - iov_.iov_len = sz; - hdr_.msg_iov = &iov_; - hdr_.msg_iovlen = 1; - hdr_.msg_control = cmsg_buffer_.data(); - hdr_.msg_controllen = cmsg_sz; - } - - struct msghdr* header() { - return &hdr_; - } - - private: - std::vector<char> buffer_; - std::vector<char> cmsg_buffer_; - struct iovec iov_ = {}; - struct msghdr hdr_ = {}; -}; - -void BM_Recvmsg(benchmark::State& state) { - int sockets[2]; - TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); - FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); - absl::Notification notification; - Message send_msg('a'), recv_msg; - - ScopedThread t([&send_msg, &send_socket, ¬ification] { - while (!notification.HasBeenNotified()) { - sendmsg(send_socket.get(), send_msg.header(), 0); - } - }); - - int64_t bytes_received = 0; - for (auto ignored : state) { - int n = recvmsg(recv_socket.get(), recv_msg.header(), 0); - TEST_CHECK(n > 0); - bytes_received += n; - } - - notification.Notify(); - recv_socket.reset(); - - state.SetBytesProcessed(bytes_received); -} - -BENCHMARK(BM_Recvmsg)->UseRealTime(); - -void BM_Sendmsg(benchmark::State& state) { - int sockets[2]; - TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); - FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); - absl::Notification notification; - Message send_msg('a'), recv_msg; - - ScopedThread t([&recv_msg, &recv_socket, ¬ification] { - while (!notification.HasBeenNotified()) { - recvmsg(recv_socket.get(), recv_msg.header(), 0); - } - }); - - int64_t bytes_sent = 0; - for (auto ignored : state) { - int n = sendmsg(send_socket.get(), send_msg.header(), 0); - TEST_CHECK(n > 0); - bytes_sent += n; - } - - notification.Notify(); - send_socket.reset(); - - state.SetBytesProcessed(bytes_sent); -} - -BENCHMARK(BM_Sendmsg)->UseRealTime(); - -void BM_Recvfrom(benchmark::State& state) { - int sockets[2]; - TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); - FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); - absl::Notification notification; - char send_buffer[kMessageSize], recv_buffer[kMessageSize]; - - ScopedThread t([&send_socket, &send_buffer, ¬ification] { - while (!notification.HasBeenNotified()) { - sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0); - } - }); - - int bytes_received = 0; - for (auto ignored : state) { - int n = recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr, - nullptr); - TEST_CHECK(n > 0); - bytes_received += n; - } - - notification.Notify(); - recv_socket.reset(); - - state.SetBytesProcessed(bytes_received); -} - -BENCHMARK(BM_Recvfrom)->UseRealTime(); - -void BM_Sendto(benchmark::State& state) { - int sockets[2]; - TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); - FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); - absl::Notification notification; - char send_buffer[kMessageSize], recv_buffer[kMessageSize]; - - ScopedThread t([&recv_socket, &recv_buffer, ¬ification] { - while (!notification.HasBeenNotified()) { - recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr, - nullptr); - } - }); - - int64_t bytes_sent = 0; - for (auto ignored : state) { - int n = sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0); - TEST_CHECK(n > 0); - bytes_sent += n; - } - - notification.Notify(); - send_socket.reset(); - - state.SetBytesProcessed(bytes_sent); -} - -BENCHMARK(BM_Sendto)->UseRealTime(); - -PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - addr.ss_family = family; - switch (family) { - case AF_INET: - reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr = - htonl(INADDR_LOOPBACK); - break; - case AF_INET6: - reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr = - in6addr_loopback; - break; - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } - return addr; -} - -// BM_RecvmsgWithControlBuf measures the performance of recvmsg when we allocate -// space for control messages. Note that we do not expect to receive any. -void BM_RecvmsgWithControlBuf(benchmark::State& state) { - auto listen_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET6)); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT(bind(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the address we're listening on, then connect to it. We need to do this - // because we're allowing the stack to pick a port for us. - ASSERT_THAT(getsockname(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - auto send_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT( - RetryEINTR(connect)(send_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Accept the connection. - auto recv_socket = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr)); - - absl::Notification notification; - Message send_msg('a'); - // Create a msghdr with a buffer allocated for control messages. - Message recv_msg(0, kMessageSize, /*cmsg_sz=*/24); - - ScopedThread t([&send_msg, &send_socket, ¬ification] { - while (!notification.HasBeenNotified()) { - sendmsg(send_socket.get(), send_msg.header(), 0); - } - }); - - int64_t bytes_received = 0; - for (auto ignored : state) { - int n = recvmsg(recv_socket.get(), recv_msg.header(), 0); - TEST_CHECK(n > 0); - bytes_received += n; - } - - notification.Notify(); - recv_socket.reset(); - - state.SetBytesProcessed(bytes_received); -} - -BENCHMARK(BM_RecvmsgWithControlBuf)->UseRealTime(); - -// BM_SendmsgTCP measures the sendmsg throughput with varying payload sizes. -// -// state.Args[0] indicates whether the underlying socket should be blocking or -// non-blocking w/ 0 indicating non-blocking and 1 to indicate blocking. -// state.Args[1] is the size of the payload to be used per sendmsg call. -void BM_SendmsgTCP(benchmark::State& state) { - auto listen_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET)); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT(bind(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the address we're listening on, then connect to it. We need to do this - // because we're allowing the stack to pick a port for us. - ASSERT_THAT(getsockname(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - auto send_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT( - RetryEINTR(connect)(send_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Accept the connection. - auto recv_socket = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr)); - - // Check if we want to run the test w/ a blocking send socket - // or non-blocking. - const int blocking = state.range(0); - if (!blocking) { - // Set the send FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(send_socket.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(send_socket.get(), F_SETFL, opts), SyscallSucceeds()); - } - - absl::Notification notification; - - // Get the buffer size we should use for this iteration of the test. - const int buf_size = state.range(1); - Message send_msg('a', buf_size), recv_msg(0, buf_size); - - ScopedThread t([&recv_msg, &recv_socket, ¬ification] { - while (!notification.HasBeenNotified()) { - TEST_CHECK(recvmsg(recv_socket.get(), recv_msg.header(), 0) >= 0); - } - }); - - int64_t bytes_sent = 0; - int ncalls = 0; - for (auto ignored : state) { - int sent = 0; - while (true) { - struct msghdr hdr = {}; - struct iovec iov = {}; - struct msghdr* snd_header = send_msg.header(); - iov.iov_base = static_cast<char*>(snd_header->msg_iov->iov_base) + sent; - iov.iov_len = snd_header->msg_iov->iov_len - sent; - hdr.msg_iov = &iov; - hdr.msg_iovlen = 1; - int n = RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0); - ncalls++; - if (n > 0) { - sent += n; - if (sent == buf_size) { - break; - } - // n can be > 0 but less than requested size. In which case we don't - // poll. - continue; - } - // Poll the fd for it to become writable. - struct pollfd poll_fd = {send_socket.get(), POLL_OUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), - SyscallSucceedsWithValue(0)); - } - bytes_sent += static_cast<int64_t>(sent); - } - - notification.Notify(); - send_socket.reset(); - state.SetBytesProcessed(bytes_sent); -} - -void Args(benchmark::internal::Benchmark* benchmark) { - for (int blocking = 0; blocking < 2; blocking++) { - for (int buf_size = 1024; buf_size <= 256 << 20; buf_size *= 2) { - benchmark->Args({blocking, buf_size}); - } - } -} - -BENCHMARK(BM_SendmsgTCP)->Apply(&Args)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/seqwrite_benchmark.cc b/test/perf/linux/seqwrite_benchmark.cc deleted file mode 100644 index af49e4477..000000000 --- a/test/perf/linux/seqwrite_benchmark.cc +++ /dev/null @@ -1,66 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// The maximum file size of the test file, when writes get beyond this point -// they wrap around. This should be large enough to blow away caches. -const uint64_t kMaxFile = 1 << 30; - -// Perform writes of various sizes sequentially to one file. Wraps around if it -// goes above a certain maximum file size. -void BM_SeqWrite(benchmark::State& state) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_WRONLY)); - - const int size = state.range(0); - std::vector<char> buf(size); - RandomizeBuffer(buf.data(), buf.size()); - - // Start writes at offset 0. - uint64_t offset = 0; - for (auto _ : state) { - TEST_CHECK(PwriteFd(fd.get(), buf.data(), buf.size(), offset) == - buf.size()); - offset += buf.size(); - // Wrap around if going above the maximum file size. - if (offset >= kMaxFile) { - offset = 0; - } - } - - state.SetBytesProcessed(static_cast<int64_t>(size) * - static_cast<int64_t>(state.iterations())); -} - -BENCHMARK(BM_SeqWrite)->Range(1, 1 << 26)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/signal_benchmark.cc b/test/perf/linux/signal_benchmark.cc deleted file mode 100644 index cec679191..000000000 --- a/test/perf/linux/signal_benchmark.cc +++ /dev/null @@ -1,61 +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. - -#include <signal.h> -#include <string.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void FixupHandler(int sig, siginfo_t* si, void* void_ctx) { - static unsigned int dataval = 0; - - // Skip the offending instruction. - ucontext_t* ctx = reinterpret_cast<ucontext_t*>(void_ctx); - ctx->uc_mcontext.gregs[REG_RAX] = reinterpret_cast<greg_t>(&dataval); -} - -void BM_FaultSignalFixup(benchmark::State& state) { - // Set up the signal handler. - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_sigaction = FixupHandler; - sa.sa_flags = SA_SIGINFO; - TEST_CHECK(sigaction(SIGSEGV, &sa, nullptr) == 0); - - // Fault, fault, fault. - for (auto _ : state) { - // Trigger the segfault. - asm volatile( - "movq $0, %%rax\n" - "movq $0x77777777, (%%rax)\n" - : - : - : "rax"); - } -} - -BENCHMARK(BM_FaultSignalFixup)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/sleep_benchmark.cc b/test/perf/linux/sleep_benchmark.cc deleted file mode 100644 index 99ef05117..000000000 --- a/test/perf/linux/sleep_benchmark.cc +++ /dev/null @@ -1,60 +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. - -#include <errno.h> -#include <sys/syscall.h> -#include <time.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Sleep for 'param' nanoseconds. -void BM_Sleep(benchmark::State& state) { - const int nanoseconds = state.range(0); - - for (auto _ : state) { - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = nanoseconds; - - int ret; - do { - ret = syscall(SYS_nanosleep, &ts, &ts); - if (ret < 0) { - TEST_CHECK(errno == EINTR); - } - } while (ret < 0); - } -} - -BENCHMARK(BM_Sleep) - ->Arg(0) - ->Arg(1) - ->Arg(1000) // 1us - ->Arg(1000 * 1000) // 1ms - ->Arg(10 * 1000 * 1000) // 10ms - ->Arg(50 * 1000 * 1000) // 50ms - ->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/stat_benchmark.cc b/test/perf/linux/stat_benchmark.cc deleted file mode 100644 index f15424482..000000000 --- a/test/perf/linux/stat_benchmark.cc +++ /dev/null @@ -1,62 +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. - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Creates a file in a nested directory hierarchy at least `depth` directories -// deep, and stats that file multiple times. -void BM_Stat(benchmark::State& state) { - // Create nested directories with given depth. - int depth = state.range(0); - const TempPath top_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string dir_path = top_dir.path(); - - while (depth-- > 0) { - // Don't use TempPath because it will make paths too long to use. - // - // The top_dir destructor will clean up this whole tree. - dir_path = JoinPath(dir_path, absl::StrCat(depth)); - ASSERT_NO_ERRNO(Mkdir(dir_path, 0755)); - } - - // Create the file that will be stat'd. - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir_path)); - - struct stat st; - for (auto _ : state) { - ASSERT_THAT(stat(file.path().c_str(), &st), SyscallSucceeds()); - } -} - -BENCHMARK(BM_Stat)->Range(1, 100)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/unlink_benchmark.cc b/test/perf/linux/unlink_benchmark.cc deleted file mode 100644 index 92243a042..000000000 --- a/test/perf/linux/unlink_benchmark.cc +++ /dev/null @@ -1,66 +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. - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Creates a directory containing `files` files, and unlinks all the files. -void BM_Unlink(benchmark::State& state) { - // Create directory with given files. - const int file_count = state.range(0); - - // We unlink all files on each iteration, but report this as a "batch" - // iteration so that reported times are per file. - TempPath dir; - while (state.KeepRunningBatch(file_count)) { - state.PauseTiming(); - // N.B. dir is declared outside the loop so that destruction of the previous - // iteration's directory occurs here, inside of PauseTiming. - dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - std::vector<TempPath> files; - for (int i = 0; i < file_count; i++) { - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - files.push_back(std::move(file)); - } - state.ResumeTiming(); - - while (!files.empty()) { - // Destructor unlinks. - files.pop_back(); - } - } - - state.SetItemsProcessed(state.iterations()); -} - -BENCHMARK(BM_Unlink)->Range(1, 100 * 1000)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/perf/linux/write_benchmark.cc b/test/perf/linux/write_benchmark.cc deleted file mode 100644 index 7b060c70e..000000000 --- a/test/perf/linux/write_benchmark.cc +++ /dev/null @@ -1,52 +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. - -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void BM_Write(benchmark::State& state) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_WRONLY)); - - const int size = state.range(0); - std::vector<char> buf(size); - RandomizeBuffer(buf.data(), size); - - for (auto _ : state) { - TEST_CHECK(PwriteFd(fd.get(), buf.data(), size, 0) == size); - } - - state.SetBytesProcessed(static_cast<int64_t>(size) * - static_cast<int64_t>(state.iterations())); -} - -BENCHMARK(BM_Write)->Range(1, 1 << 26)->UseRealTime(); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/root/BUILD b/test/root/BUILD deleted file mode 100644 index 8d9fff578..000000000 --- a/test/root/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "root", - srcs = ["root.go"], -) - -go_test( - name = "root_test", - size = "small", - srcs = [ - "cgroup_test.go", - "chroot_test.go", - "crictl_test.go", - "main_test.go", - "oom_score_adj_test.go", - "runsc_test.go", - ], - data = [ - "//runsc", - ], - library = ":root", - tags = [ - "local", - "manual", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/cleanup", - "//pkg/test/criutil", - "//pkg/test/dockerutil", - "//pkg/test/testutil", - "//runsc/cgroup", - "//runsc/container", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/test/root/cgroup_test.go b/test/root/cgroup_test.go deleted file mode 100644 index a74d6b1c1..000000000 --- a/test/root/cgroup_test.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package root - -import ( - "bufio" - "context" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/cgroup" -) - -func verifyPid(pid int, path string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - var gots []int - scanner := bufio.NewScanner(f) - for scanner.Scan() { - got, err := strconv.Atoi(scanner.Text()) - if err != nil { - return err - } - if got == pid { - return nil - } - gots = append(gots, got) - } - if scanner.Err() != nil { - return scanner.Err() - } - return fmt.Errorf("got: %v, want: %d", gots, pid) -} - -func TestMemCgroup(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start a new container and allocate the specified about of memory. - allocMemSize := 128 << 20 - allocMemLimit := 2 * allocMemSize - - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/ubuntu", - Memory: allocMemLimit, // Must be in bytes. - }, "python3", "-c", fmt.Sprintf("import time; s = 'a' * %d; time.sleep(100)", allocMemSize)); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Extract the ID to lookup the cgroup. - gid := d.ID() - t.Logf("cgroup ID: %s", gid) - - // Wait when the container will allocate memory. - memUsage := 0 - start := time.Now() - for time.Since(start) < 30*time.Second { - // Sleep for a brief period of time after spawning the - // container (so that Docker can create the cgroup etc. - // or after looping below (so the application can start). - time.Sleep(100 * time.Millisecond) - - // Read the cgroup memory limit. - path := filepath.Join("/sys/fs/cgroup/memory/docker", gid, "memory.limit_in_bytes") - outRaw, err := ioutil.ReadFile(path) - if err != nil { - // It's possible that the container does not exist yet. - continue - } - out := strings.TrimSpace(string(outRaw)) - memLimit, err := strconv.Atoi(out) - if err != nil { - t.Fatalf("Atoi(%v): %v", out, err) - } - if memLimit != allocMemLimit { - // The group may not have had the correct limit set yet. - continue - } - - // Read the cgroup memory usage. - path = filepath.Join("/sys/fs/cgroup/memory/docker", gid, "memory.max_usage_in_bytes") - outRaw, err = ioutil.ReadFile(path) - if err != nil { - t.Fatalf("error reading usage: %v", err) - } - out = strings.TrimSpace(string(outRaw)) - memUsage, err = strconv.Atoi(out) - if err != nil { - t.Fatalf("Atoi(%v): %v", out, err) - } - t.Logf("read usage: %v, wanted: %v", memUsage, allocMemSize) - - // Are we done? - if memUsage >= allocMemSize { - return - } - } - - t.Fatalf("%vMB is less than %vMB", memUsage>>20, allocMemSize>>20) -} - -// TestCgroup sets cgroup options and checks that cgroup was properly configured. -func TestCgroup(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // This is not a comprehensive list of attributes. - // - // Note that we are specifically missing cpusets, which fail if specified. - // In any case, it's unclear if cpusets can be reliably tested here: these - // are often run on a single core virtual machine, and there is only a single - // CPU available in our current set, and every container's set. - attrs := []struct { - field string - value int64 - ctrl string - file string - want string - skipIfNotFound bool - }{ - { - field: "cpu-shares", - value: 1000, - ctrl: "cpu", - file: "cpu.shares", - want: "1000", - }, - { - field: "cpu-period", - value: 2000, - ctrl: "cpu", - file: "cpu.cfs_period_us", - want: "2000", - }, - { - field: "cpu-quota", - value: 3000, - ctrl: "cpu", - file: "cpu.cfs_quota_us", - want: "3000", - }, - { - field: "kernel-memory", - value: 100 << 20, - ctrl: "memory", - file: "memory.kmem.limit_in_bytes", - want: "104857600", - }, - { - field: "memory", - value: 1 << 30, - ctrl: "memory", - file: "memory.limit_in_bytes", - want: "1073741824", - }, - { - field: "memory-reservation", - value: 500 << 20, - ctrl: "memory", - file: "memory.soft_limit_in_bytes", - want: "524288000", - }, - { - field: "memory-swap", - value: 2 << 30, - ctrl: "memory", - file: "memory.memsw.limit_in_bytes", - want: "2147483648", - skipIfNotFound: true, // swap may be disabled on the machine. - }, - { - field: "memory-swappiness", - value: 5, - ctrl: "memory", - file: "memory.swappiness", - want: "5", - }, - { - field: "blkio-weight", - value: 750, - ctrl: "blkio", - file: "blkio.weight", - want: "750", - skipIfNotFound: true, // blkio groups may not be available. - }, - { - field: "pids-limit", - value: 1000, - ctrl: "pids", - file: "pids.max", - want: "1000", - }, - } - - // Make configs. - conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "10000") - - // Add Cgroup arguments to configs. - for _, attr := range attrs { - switch attr.field { - case "cpu-shares": - hostconf.Resources.CPUShares = attr.value - case "cpu-period": - hostconf.Resources.CPUPeriod = attr.value - case "cpu-quota": - hostconf.Resources.CPUQuota = attr.value - case "kernel-memory": - hostconf.Resources.KernelMemory = attr.value - case "memory": - hostconf.Resources.Memory = attr.value - case "memory-reservation": - hostconf.Resources.MemoryReservation = attr.value - case "memory-swap": - hostconf.Resources.MemorySwap = attr.value - case "memory-swappiness": - val := attr.value - hostconf.Resources.MemorySwappiness = &val - case "blkio-weight": - hostconf.Resources.BlkioWeight = uint16(attr.value) - case "pids-limit": - val := attr.value - hostconf.Resources.PidsLimit = &val - } - } - - // Create container. - if err := d.CreateFrom(ctx, "basic/alpine", conf, hostconf, nil); err != nil { - t.Fatalf("create failed with: %v", err) - } - - // Start container. - if err := d.Start(ctx); err != nil { - t.Fatalf("start failed with: %v", err) - } - - // Lookup the relevant cgroup ID. - gid := d.ID() - t.Logf("cgroup ID: %s", gid) - - // Check list of attributes defined above. - for _, attr := range attrs { - path := filepath.Join("/sys/fs/cgroup", attr.ctrl, "docker", gid, attr.file) - out, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) && attr.skipIfNotFound { - t.Logf("skipped %s/%s", attr.ctrl, attr.file) - continue - } - t.Fatalf("failed to read %q: %v", path, err) - } - if got := strings.TrimSpace(string(out)); got != attr.want { - t.Errorf("field: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.field, attr.ctrl, attr.file, got, attr.want) - } - } - - // Check that sandbox is inside cgroup. - controllers := []string{ - "blkio", - "cpu", - "cpuset", - "memory", - "net_cls", - "net_prio", - "devices", - "freezer", - "perf_event", - "pids", - "systemd", - } - pid, err := d.SandboxPid(ctx) - if err != nil { - t.Fatalf("SandboxPid: %v", err) - } - for _, ctrl := range controllers { - path := filepath.Join("/sys/fs/cgroup", ctrl, "docker", gid, "cgroup.procs") - if err := verifyPid(pid, path); err != nil { - t.Errorf("cgroup control %q processes: %v", ctrl, err) - } - } -} - -// TestCgroupParent sets the "CgroupParent" option and checks that the child and parent's -// cgroups are created correctly relative to each other. -func TestCgroupParent(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Construct a known cgroup name. - parent := testutil.RandomID("runsc-") - conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "10000") - hostconf.Resources.CgroupParent = parent - - if err := d.CreateFrom(ctx, "basic/alpine", conf, hostconf, nil); err != nil { - t.Fatalf("create failed with: %v", err) - } - - if err := d.Start(ctx); err != nil { - t.Fatalf("start failed with: %v", err) - } - - // Extract the ID to look up the cgroup. - gid := d.ID() - t.Logf("cgroup ID: %s", gid) - - // Check that sandbox is inside cgroup. - pid, err := d.SandboxPid(ctx) - if err != nil { - t.Fatalf("SandboxPid: %v", err) - } - - // Finds cgroup for the sandbox's parent process to check that cgroup is - // created in the right location relative to the parent. - cmd := fmt.Sprintf("grep PPid: /proc/%d/status | sed 's/PPid:\\s//'", pid) - ppid, err := exec.Command("bash", "-c", cmd).CombinedOutput() - if err != nil { - t.Fatalf("Executing %q: %v", cmd, err) - } - cgroups, err := cgroup.LoadPaths(strings.TrimSpace(string(ppid))) - if err != nil { - t.Fatalf("cgroup.LoadPath(%s): %v", ppid, err) - } - path := filepath.Join("/sys/fs/cgroup/memory", cgroups["memory"], parent, gid, "cgroup.procs") - if err := verifyPid(pid, path); err != nil { - t.Errorf("cgroup control %q processes: %v", "memory", err) - } -} diff --git a/test/root/chroot_test.go b/test/root/chroot_test.go deleted file mode 100644 index 58fcd6f08..000000000 --- a/test/root/chroot_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package root is used for tests that requires sysadmin privileges run. -package root - -import ( - "context" - "fmt" - "io/ioutil" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" -) - -// TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned -// up after the sandbox is destroyed. -func TestChroot(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "10000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - pid, err := d.SandboxPid(ctx) - if err != nil { - t.Fatalf("Docker.SandboxPid(): %v", err) - } - - // Check that sandbox is chroot'ed. - procRoot := filepath.Join("/proc", strconv.Itoa(pid), "root") - chroot, err := filepath.EvalSymlinks(procRoot) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) - } - if chroot != "/" { - t.Errorf("sandbox is not chroot'd, it should be inside: /, got: %q", chroot) - } - - path, err := filepath.EvalSymlinks(filepath.Join("/proc", strconv.Itoa(pid), "cwd")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) - } - if chroot != path { - t.Errorf("sandbox current dir is wrong, want: %q, got: %q", chroot, path) - } - - fi, err := ioutil.ReadDir(procRoot) - if err != nil { - t.Fatalf("error listing %q: %v", chroot, err) - } - if want, got := 1, len(fi); want != got { - t.Fatalf("chroot dir got %d entries, want %d", got, want) - } - - // chroot dir is prepared by runsc and should contains only /proc. - if fi[0].Name() != "proc" { - t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc") - } - - d.CleanUp(ctx) -} - -func TestChrootGofer(t *testing.T) { - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/alpine", - }, "sleep", "10000"); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // It's tricky to find gofers. Get sandbox PID first, then find parent. From - // parent get all immediate children, remove the sandbox, and everything else - // are gofers. - sandPID, err := d.SandboxPid(ctx) - if err != nil { - t.Fatalf("Docker.SandboxPid(): %v", err) - } - - // Find sandbox's parent PID. - cmd := fmt.Sprintf("grep PPid /proc/%d/status | awk '{print $2}'", sandPID) - parent, err := exec.Command("sh", "-c", cmd).CombinedOutput() - if err != nil { - t.Fatalf("failed to fetch runsc (%d) parent PID: %v, out:\n%s", sandPID, err, string(parent)) - } - parentPID, err := strconv.Atoi(strings.TrimSpace(string(parent))) - if err != nil { - t.Fatalf("failed to parse PPID %q: %v", string(parent), err) - } - - // Get all children from parent. - childrenOut, err := exec.Command("/usr/bin/pgrep", "-P", strconv.Itoa(parentPID)).CombinedOutput() - if err != nil { - t.Fatalf("failed to fetch containerd-shim children: %v", err) - } - children := strings.Split(strings.TrimSpace(string(childrenOut)), "\n") - - // This where the root directory is mapped on the host and that's where the - // gofer must have chroot'd to. - root := "/root" - - for _, child := range children { - childPID, err := strconv.Atoi(child) - if err != nil { - t.Fatalf("failed to parse child PID %q: %v", child, err) - } - if childPID == sandPID { - // Skip the sandbox, all other immediate children are gofers. - continue - } - - // Check that gofer is chroot'ed. - chroot, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "root")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) - } - if root != chroot { - t.Errorf("gofer chroot is wrong, want: %q, got: %q", root, chroot) - } - - path, err := filepath.EvalSymlinks(filepath.Join("/proc", child, "cwd")) - if err != nil { - t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) - } - if root != path { - t.Errorf("gofer current dir is wrong, want: %q, got: %q", root, path) - } - } -} diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go deleted file mode 100644 index c26dc8577..000000000 --- a/test/root/crictl_test.go +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package root - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path" - "regexp" - "strconv" - "strings" - "sync" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/test/criutil" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// Tests for crictl have to be run as root (rather than in a user namespace) -// because crictl creates named network namespaces in /var/run/netns/. - -// Sandbox returns a JSON config for a simple sandbox. Sandbox names must be -// unique so different names should be used when running tests on the same -// containerd instance. -func Sandbox(name string) string { - // Sandbox is a default JSON config for a sandbox. - s := map[string]interface{}{ - "metadata": map[string]string{ - "name": name, - "namespace": "default", - "uid": testutil.RandomID(""), - }, - "linux": map[string]string{}, - "log_directory": "/tmp", - } - - v, err := json.Marshal(s) - if err != nil { - // This shouldn't happen. - panic(err) - } - return string(v) -} - -// SimpleSpec returns a JSON config for a simple container that runs the -// specified command in the specified image. -func SimpleSpec(name, image string, cmd []string, extra map[string]interface{}) string { - s := map[string]interface{}{ - "metadata": map[string]string{ - "name": name, - }, - "image": map[string]string{ - "image": testutil.ImageByName(image), - }, - // Log files are not deleted after root tests are run. Log to random - // paths to ensure logs are fresh. - "log_path": fmt.Sprintf("%s.log", testutil.RandomID(name)), - "stdin": false, - "tty": false, - } - if len(cmd) > 0 { // Omit if empty. - s["command"] = cmd - } - for k, v := range extra { - s[k] = v // Extra settings. - } - v, err := json.Marshal(s) - if err != nil { - // This shouldn't happen. - panic(err) - } - return string(v) -} - -// Httpd is a JSON config for an httpd container. -var Httpd = SimpleSpec("httpd", "basic/httpd", nil, nil) - -// TestCrictlSanity refers to b/112433158. -func TestCrictlSanity(t *testing.T) { - // Setup containerd and crictl. - crictl, cleanup, err := setup(t) - if err != nil { - t.Fatalf("failed to setup crictl: %v", err) - } - defer cleanup() - podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), Httpd) - if err != nil { - t.Fatalf("start failed: %v", err) - } - - // Look for the httpd page. - if err = httpGet(crictl, podID, "index.html"); err != nil { - t.Fatalf("failed to get page: %v", err) - } - - // Stop everything. - if err := crictl.StopPodAndContainer(podID, contID); err != nil { - t.Fatalf("stop failed: %v", err) - } -} - -// HttpdMountPaths is a JSON config for an httpd container with additional -// mounts. -var HttpdMountPaths = SimpleSpec("httpd", "basic/httpd", nil, map[string]interface{}{ - "mounts": []map[string]interface{}{ - { - "container_path": "/var/run/secrets/kubernetes.io/serviceaccount", - "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/volumes/kubernetes.io~secret/default-token-2rpfx", - "readonly": true, - }, - { - "container_path": "/etc/hosts", - "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/etc-hosts", - "readonly": false, - }, - { - "container_path": "/dev/termination-log", - "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064/containers/httpd/d1709580", - "readonly": false, - }, - { - "container_path": "/usr/local/apache2/htdocs/test", - "host_path": "/var/lib/kubelet/pods/82bae206-cdf5-11e8-b245-8cdcd43ac064", - "readonly": true, - }, - }, - "linux": map[string]interface{}{}, -}) - -// TestMountPaths refers to b/117635704. -func TestMountPaths(t *testing.T) { - // Setup containerd and crictl. - crictl, cleanup, err := setup(t) - if err != nil { - t.Fatalf("failed to setup crictl: %v", err) - } - defer cleanup() - podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), HttpdMountPaths) - if err != nil { - t.Fatalf("start failed: %v", err) - } - - // Look for the directory available at /test. - if err = httpGet(crictl, podID, "test"); err != nil { - t.Fatalf("failed to get page: %v", err) - } - - // Stop everything. - if err := crictl.StopPodAndContainer(podID, contID); err != nil { - t.Fatalf("stop failed: %v", err) - } -} - -// TestMountPaths refers to b/118728671. -func TestMountOverSymlinks(t *testing.T) { - // Setup containerd and crictl. - crictl, cleanup, err := setup(t) - if err != nil { - t.Fatalf("failed to setup crictl: %v", err) - } - defer cleanup() - - spec := SimpleSpec("busybox", "basic/resolv", []string{"sleep", "1000"}, nil) - podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/resolv", Sandbox("default"), spec) - if err != nil { - t.Fatalf("start failed: %v", err) - } - - out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf") - if err != nil { - t.Fatalf("readlink failed: %v, out: %s", err, out) - } - if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) { - t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out)) - } - - etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf") - if err != nil { - t.Fatalf("cat failed: %v, out: %s", err, etc) - } - tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf") - if err != nil { - t.Fatalf("cat failed: %v, out: %s", err, out) - } - if tmp != etc { - t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp)) - } - - // Stop everything. - if err := crictl.StopPodAndContainer(podID, contID); err != nil { - t.Fatalf("stop failed: %v", err) - } -} - -// TestHomeDir tests that the HOME environment variable is set for -// Pod containers. -func TestHomeDir(t *testing.T) { - // Setup containerd and crictl. - crictl, cleanup, err := setup(t) - if err != nil { - t.Fatalf("failed to setup crictl: %v", err) - } - defer cleanup() - - // Note that container ID returned here is a sub-container. All Pod - // containers are sub-containers. The root container of the sandbox is the - // pause container. - t.Run("sub-container", func(t *testing.T) { - contSpec := SimpleSpec("subcontainer", "basic/busybox", []string{"sh", "-c", "echo $HOME"}, nil) - podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("subcont-sandbox"), contSpec) - if err != nil { - t.Fatalf("start failed: %v", err) - } - - out, err := crictl.Logs(contID) - if err != nil { - t.Fatalf("failed retrieving container logs: %v, out: %s", err, out) - } - if got, want := strings.TrimSpace(string(out)), "/root"; got != want { - t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want) - } - - // Stop everything; note that the pod may have already stopped. - crictl.StopPodAndContainer(podID, contID) - }) - - // Tests that HOME is set for the exec process. - t.Run("exec", func(t *testing.T) { - contSpec := SimpleSpec("exec", "basic/busybox", []string{"sleep", "1000"}, nil) - podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("exec-sandbox"), contSpec) - if err != nil { - t.Fatalf("start failed: %v", err) - } - - out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME") - if err != nil { - t.Fatalf("failed retrieving container logs: %v, out: %s", err, out) - } - if got, want := strings.TrimSpace(string(out)), "/root"; got != want { - t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want) - } - - // Stop everything. - if err := crictl.StopPodAndContainer(podID, contID); err != nil { - t.Fatalf("stop failed: %v", err) - } - }) -} - -const containerdRuntime = "runsc" - -// Template is the containerd configuration file that configures containerd with -// the gVisor shim, Note that the v2 shim binary name must be -// containerd-shim-<runtime>-v1. -const template = ` -disabled_plugins = ["restart"] -[plugins.cri] - disable_tcp_service = true -[plugins.linux] - shim_debug = true -[plugins.cri.containerd.runtimes.` + containerdRuntime + `] - runtime_type = "io.containerd.` + containerdRuntime + `.v1" -[plugins.cri.containerd.runtimes.` + containerdRuntime + `.options] - TypeUrl = "io.containerd.` + containerdRuntime + `.v1.options" -` - -// setup sets up before a test. Specifically it: -// * Creates directories and a socket for containerd to utilize. -// * Runs containerd and waits for it to reach a "ready" state for testing. -// * Returns a cleanup function that should be called at the end of the test. -func setup(t *testing.T) (*criutil.Crictl, func(), error) { - // Create temporary containerd root and state directories, and a socket - // via which crictl and containerd communicate. - containerdRoot, err := ioutil.TempDir(testutil.TmpDir(), "containerd-root") - if err != nil { - t.Fatalf("failed to create containerd root: %v", err) - } - cu := cleanup.Make(func() { os.RemoveAll(containerdRoot) }) - defer cu.Clean() - t.Logf("Using containerd root: %s", containerdRoot) - - containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state") - if err != nil { - t.Fatalf("failed to create containerd state: %v", err) - } - cu.Add(func() { os.RemoveAll(containerdState) }) - t.Logf("Using containerd state: %s", containerdState) - - sockDir, err := ioutil.TempDir(testutil.TmpDir(), "containerd-sock") - if err != nil { - t.Fatalf("failed to create containerd socket directory: %v", err) - } - cu.Add(func() { os.RemoveAll(sockDir) }) - sockAddr := path.Join(sockDir, "test.sock") - t.Logf("Using containerd socket: %s", sockAddr) - - // Extract the containerd version. - versionCmd := exec.Command(getContainerd(), "-v") - out, err := versionCmd.CombinedOutput() - if err != nil { - t.Fatalf("error extracting containerd version: %v (%s)", err, string(out)) - } - r := regexp.MustCompile(" v([0-9]+)\\.([0-9]+)\\.([0-9+])") - vs := r.FindStringSubmatch(string(out)) - if len(vs) != 4 { - t.Fatalf("error unexpected version string: %s", string(out)) - } - major, err := strconv.ParseUint(vs[1], 10, 64) - if err != nil { - t.Fatalf("error parsing containerd major version: %v (%s)", err, string(out)) - } - minor, err := strconv.ParseUint(vs[2], 10, 64) - if err != nil { - t.Fatalf("error parsing containerd minor version: %v (%s)", err, string(out)) - } - t.Logf("Using containerd version: %d.%d", major, minor) - - // Check if containerd supports shim v2. - if major < 1 || (major == 1 && minor <= 1) { - t.Skipf("skipping incompatible containerd (want at least 1.2, got %d.%d)", major, minor) - } - - // We rewrite a configuration. This is based on the current docker - // configuration for the runtime under test. - runtime, err := dockerutil.RuntimePath() - if err != nil { - t.Fatalf("error discovering runtime path: %v", err) - } - t.Logf("Using runtime: %v", runtime) - - // Construct a PATH that includes the runtime directory. This is - // because the shims will be installed there, and containerd may infer - // the binary name and search the PATH. - runtimeDir := path.Dir(runtime) - modifiedPath, ok := os.LookupEnv("PATH") - if ok { - modifiedPath = ":" + modifiedPath // We prepend below. - } - modifiedPath = path.Dir(getContainerd()) + modifiedPath - modifiedPath = runtimeDir + ":" + modifiedPath - t.Logf("Using PATH: %v", modifiedPath) - - // Generate the configuration for the test. - t.Logf("Using config: %s", template) - configFile, configCleanup, err := testutil.WriteTmpFile("containerd-config", template) - if err != nil { - t.Fatalf("failed to write containerd config") - } - cu.Add(configCleanup) - - // Start containerd. - args := []string{ - getContainerd(), - "--config", configFile, - "--log-level", "debug", - "--root", containerdRoot, - "--state", containerdState, - "--address", sockAddr, - } - t.Logf("Using args: %s", strings.Join(args, " ")) - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = append(os.Environ(), "PATH="+modifiedPath) - - // Include output in logs. - stderrPipe, err := cmd.StderrPipe() - if err != nil { - t.Fatalf("failed to create stderr pipe: %v", err) - } - cu.Add(func() { stderrPipe.Close() }) - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - t.Fatalf("failed to create stdout pipe: %v", err) - } - cu.Add(func() { stdoutPipe.Close() }) - var ( - wg sync.WaitGroup - stderr bytes.Buffer - stdout bytes.Buffer - ) - startupR, startupW := io.Pipe() - wg.Add(2) - go func() { - defer wg.Done() - io.Copy(io.MultiWriter(startupW, &stderr), stderrPipe) - }() - go func() { - defer wg.Done() - io.Copy(io.MultiWriter(startupW, &stdout), stdoutPipe) - }() - cu.Add(func() { - wg.Wait() - t.Logf("containerd stdout: %s", stdout.String()) - t.Logf("containerd stderr: %s", stderr.String()) - }) - - // Start the process. - if err := cmd.Start(); err != nil { - t.Fatalf("failed running containerd: %v", err) - } - - // Wait for containerd to boot. - if err := testutil.WaitUntilRead(startupR, "Start streaming server", 10*time.Second); err != nil { - t.Fatalf("failed to start containerd: %v", err) - } - - // Discard all subsequent data. - go io.Copy(ioutil.Discard, startupR) - - // Create the crictl interface. - cc := criutil.NewCrictl(t, sockAddr) - cu.Add(cc.CleanUp) - - // Kill must be the last cleanup (as it will be executed first). - cu.Add(func() { - // Best effort: ignore errors. - testutil.KillCommand(cmd) - }) - - return cc, cu.Release(), nil -} - -// httpGet GETs the contents of a file served from a pod on port 80. -func httpGet(crictl *criutil.Crictl, podID, filePath string) error { - // Get the IP of the httpd server. - ip, err := crictl.PodIP(podID) - if err != nil { - return fmt.Errorf("failed to get IP from pod %q: %v", podID, err) - } - - // GET the page. We may be waiting for the server to start, so retry - // with a timeout. - var resp *http.Response - cb := func() error { - r, err := http.Get(fmt.Sprintf("http://%s", path.Join(ip, filePath))) - resp = r - return err - } - if err := testutil.Poll(cb, 20*time.Second); err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return fmt.Errorf("bad status returned: %d", resp.StatusCode) - } - return nil -} - -func getContainerd() string { - // Use the local path if it exists, otherwise, use the system one. - if _, err := os.Stat("/usr/local/bin/containerd"); err == nil { - return "/usr/local/bin/containerd" - } - return "/usr/bin/containerd" -} diff --git a/test/root/main_test.go b/test/root/main_test.go deleted file mode 100644 index 9fb17e0dd..000000000 --- a/test/root/main_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package root - -import ( - "flag" - "fmt" - "os" - "testing" - - "github.com/syndtr/gocapability/capability" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/runsc/specutils" -) - -// TestMain is the main function for root tests. This function checks the -// supported docker version, required capabilities, and configures the executable -// path for runsc. -func TestMain(m *testing.M) { - flag.Parse() - - if !specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_DAC_OVERRIDE) { - fmt.Println("Test requires sysadmin privileges to run. Try again with sudo.") - os.Exit(1) - } - - dockerutil.EnsureSupportedDockerVersion() - - // Configure exe for tests. - path, err := dockerutil.RuntimePath() - if err != nil { - panic(err.Error()) - } - specutils.ExePath = path - - os.Exit(m.Run()) -} diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go deleted file mode 100644 index 0dcc0fdea..000000000 --- a/test/root/oom_score_adj_test.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package root - -import ( - "fmt" - "os" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/container" - "gvisor.dev/gvisor/runsc/specutils" -) - -var ( - maxOOMScoreAdj = 1000 - highOOMScoreAdj = 500 - lowOOMScoreAdj = -500 - minOOMScoreAdj = -1000 -) - -// Tests for oom_score_adj have to be run as root (rather than in a user -// namespace) because we need to adjust oom_score_adj for PIDs other than our -// own and test values below 0. - -// TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a -// single container sandbox. -func TestOOMScoreAdjSingle(t *testing.T) { - parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) - if err != nil { - t.Fatalf("getting parent oom_score_adj: %v", err) - } - - testCases := []struct { - Name string - - // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then - // no value is set. - OOMScoreAdj *int - }{ - { - Name: "max", - OOMScoreAdj: &maxOOMScoreAdj, - }, - { - Name: "high", - OOMScoreAdj: &highOOMScoreAdj, - }, - { - Name: "low", - OOMScoreAdj: &lowOOMScoreAdj, - }, - { - Name: "min", - OOMScoreAdj: &minOOMScoreAdj, - }, - { - Name: "nil", - OOMScoreAdj: &parentOOMScoreAdj, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - id := testutil.RandomContainerID() - s := testutil.NewSpecWithArgs("sleep", "1000") - s.Process.OOMScoreAdj = testCase.OOMScoreAdj - - containers, cleanup, err := startContainers(t, []*specs.Spec{s}, []string{id}) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - c := containers[0] - - // Verify the gofer's oom_score_adj - if testCase.OOMScoreAdj != nil { - goferScore, err := specutils.GetOOMScoreAdj(c.GoferPid) - if err != nil { - t.Fatalf("error reading gofer oom_score_adj: %v", err) - } - if goferScore != *testCase.OOMScoreAdj { - t.Errorf("gofer oom_score_adj got: %d, want: %d", goferScore, *testCase.OOMScoreAdj) - } - - // Verify the sandbox's oom_score_adj. - // - // The sandbox should be the same for all containers so just use - // the first one. - sandboxPid := c.Sandbox.Pid - sandboxScore, err := specutils.GetOOMScoreAdj(sandboxPid) - if err != nil { - t.Fatalf("error reading sandbox oom_score_adj: %v", err) - } - if sandboxScore != *testCase.OOMScoreAdj { - t.Errorf("sandbox oom_score_adj got: %d, want: %d", sandboxScore, *testCase.OOMScoreAdj) - } - } - }) - } -} - -// TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a -// multi-container sandbox. -func TestOOMScoreAdjMulti(t *testing.T) { - parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) - if err != nil { - t.Fatalf("getting parent oom_score_adj: %v", err) - } - - testCases := []struct { - Name string - - // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then - // no value is set. One value for each container. The first value is the - // root container. - OOMScoreAdj []*int - - // Expected is the expected oom_score_adj of the sandbox. If nil, then - // this value is ignored. - Expected *int - - // Remove is a set of container indexes to remove from the sandbox. - Remove []int - - // ExpectedAfterRemove is the expected oom_score_adj of the sandbox - // after containers are removed. Ignored if nil. - ExpectedAfterRemove *int - }{ - // A single container CRI test case. This should not happen in - // practice as there should be at least one container besides the pause - // container. However, we include a test case to ensure sane behavior. - { - Name: "single", - OOMScoreAdj: []*int{&highOOMScoreAdj}, - Expected: &parentOOMScoreAdj, - }, - { - Name: "multi_no_value", - OOMScoreAdj: []*int{nil, nil, nil}, - Expected: &parentOOMScoreAdj, - }, - { - Name: "multi_non_nil_root", - OOMScoreAdj: []*int{&minOOMScoreAdj, nil, nil}, - Expected: &parentOOMScoreAdj, - }, - { - Name: "multi_value", - OOMScoreAdj: []*int{&minOOMScoreAdj, &highOOMScoreAdj, &lowOOMScoreAdj}, - // The lowest value excluding the root container is expected. - Expected: &lowOOMScoreAdj, - }, - { - Name: "multi_min_value", - OOMScoreAdj: []*int{&minOOMScoreAdj, &lowOOMScoreAdj}, - // The lowest value excluding the root container is expected. - Expected: &lowOOMScoreAdj, - }, - { - Name: "multi_max_value", - OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, - // The lowest value excluding the root container is expected. - Expected: &highOOMScoreAdj, - }, - { - Name: "remove_adjusted", - OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, - // The lowest value excluding the root container is expected. - Expected: &highOOMScoreAdj, - // Remove highOOMScoreAdj container. - Remove: []int{2}, - ExpectedAfterRemove: &maxOOMScoreAdj, - }, - { - // This test removes all non-root sandboxes with a specified oomScoreAdj. - Name: "remove_to_nil", - OOMScoreAdj: []*int{&minOOMScoreAdj, nil, &lowOOMScoreAdj}, - Expected: &lowOOMScoreAdj, - // Remove lowOOMScoreAdj container. - Remove: []int{2}, - // The oom_score_adj expected after remove is that of the parent process. - ExpectedAfterRemove: &parentOOMScoreAdj, - }, - { - Name: "remove_no_effect", - OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, - // The lowest value excluding the root container is expected. - Expected: &highOOMScoreAdj, - // Remove the maxOOMScoreAdj container. - Remove: []int{1}, - ExpectedAfterRemove: &highOOMScoreAdj, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - var cmds [][]string - var oomScoreAdj []*int - var toRemove []string - - for _, oomScore := range testCase.OOMScoreAdj { - oomScoreAdj = append(oomScoreAdj, oomScore) - cmds = append(cmds, []string{"sleep", "100"}) - } - - specs, ids := createSpecs(cmds...) - for i, spec := range specs { - // Ensure the correct value is set, including no value. - spec.Process.OOMScoreAdj = oomScoreAdj[i] - - for _, j := range testCase.Remove { - if i == j { - toRemove = append(toRemove, ids[i]) - } - } - } - - containers, cleanup, err := startContainers(t, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - for i, c := range containers { - if oomScoreAdj[i] != nil { - // Verify the gofer's oom_score_adj - score, err := specutils.GetOOMScoreAdj(c.GoferPid) - if err != nil { - t.Fatalf("error reading gofer oom_score_adj: %v", err) - } - if score != *oomScoreAdj[i] { - t.Errorf("gofer oom_score_adj got: %d, want: %d", score, *oomScoreAdj[i]) - } - } - } - - // Verify the sandbox's oom_score_adj. - // - // The sandbox should be the same for all containers so just use - // the first one. - sandboxPid := containers[0].Sandbox.Pid - if testCase.Expected != nil { - score, err := specutils.GetOOMScoreAdj(sandboxPid) - if err != nil { - t.Fatalf("error reading sandbox oom_score_adj: %v", err) - } - if score != *testCase.Expected { - t.Errorf("sandbox oom_score_adj got: %d, want: %d", score, *testCase.Expected) - } - } - - if len(toRemove) == 0 { - return - } - - // Remove containers. - for _, removeID := range toRemove { - for _, c := range containers { - if c.ID == removeID { - c.Destroy() - } - } - } - - // Check the new adjusted oom_score_adj. - if testCase.ExpectedAfterRemove != nil { - scoreAfterRemove, err := specutils.GetOOMScoreAdj(sandboxPid) - if err != nil { - t.Fatalf("error reading sandbox oom_score_adj: %v", err) - } - if scoreAfterRemove != *testCase.ExpectedAfterRemove { - t.Errorf("sandbox oom_score_adj got: %d, want: %d", scoreAfterRemove, *testCase.ExpectedAfterRemove) - } - } - }) - } -} - -func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) { - var specs []*specs.Spec - var ids []string - rootID := testutil.RandomContainerID() - - for i, cmd := range cmds { - spec := testutil.NewSpecWithArgs(cmd...) - if i == 0 { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox, - } - ids = append(ids, rootID) - } else { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer, - specutils.ContainerdSandboxIDAnnotation: rootID, - } - ids = append(ids, testutil.RandomContainerID()) - } - specs = append(specs, spec) - } - return specs, ids -} - -func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) { - var containers []*container.Container - - // All containers must share the same root. - rootDir, clean, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - cu := cleanup.Make(clean) - defer cu.Clean() - - // Point this to from the configuration. - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - for i, spec := range specs { - bundleDir, clean, err := testutil.SetupBundleDir(spec) - if err != nil { - return nil, nil, fmt.Errorf("error setting up bundle: %v", err) - } - cu.Add(clean) - - args := container.Args{ - ID: ids[i], - Spec: spec, - BundleDir: bundleDir, - } - cont, err := container.New(conf, args) - if err != nil { - return nil, nil, fmt.Errorf("error creating container: %v", err) - } - containers = append(containers, cont) - - if err := cont.Start(conf); err != nil { - return nil, nil, fmt.Errorf("error starting container: %v", err) - } - } - - return containers, cu.Release(), nil -} diff --git a/test/root/root.go b/test/root/root.go deleted file mode 100644 index 441fa5e2e..000000000 --- a/test/root/root.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package root is used for tests that requires sysadmin privileges run. First, -// follow the setup instruction in runsc/test/README.md. You should also have -// docker, containerd, and crictl installed. To run these tests from the -// project root directory: -// -// make root-tests -package root diff --git a/test/root/runsc_test.go b/test/root/runsc_test.go deleted file mode 100644 index 25204bebb..000000000 --- a/test/root/runsc_test.go +++ /dev/null @@ -1,151 +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 root - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/cenkalti/backoff" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/specutils" -) - -// TestDoKill checks that when "runsc do..." is killed, the sandbox process is -// also terminated. This ensures that parent death signal is propagate to the -// sandbox process correctly. -func TestDoKill(t *testing.T) { - // Make the sandbox process be reparented here when it's killed, so we can - // wait for it. - if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil { - t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err) - } - - cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000") - buf := &bytes.Buffer{} - cmd.Stdout = buf - cmd.Stderr = buf - cmd.Start() - - var pid int - findSandbox := func() error { - var err error - pid, err = sandboxPid(cmd.Process.Pid) - if err != nil { - return &backoff.PermanentError{Err: err} - } - if pid == 0 { - return fmt.Errorf("sandbox process not found") - } - return nil - } - if err := testutil.Poll(findSandbox, 10*time.Second); err != nil { - t.Fatalf("failed to find sandbox: %v", err) - } - t.Logf("Found sandbox, pid: %d", pid) - - if err := cmd.Process.Kill(); err != nil { - t.Fatalf("failed to kill run process: %v", err) - } - cmd.Wait() - t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String()) - - ch := make(chan struct{}) - go func() { - defer func() { ch <- struct{}{} }() - t.Logf("Waiting for sandbox process (%d) termination", pid) - if _, err := unix.Wait4(pid, nil, 0, nil); err != nil { - t.Errorf("error waiting for sandbox process (%d): %v", pid, err) - } - }() - select { - case <-ch: - // Done - case <-time.After(5 * time.Second): - t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid) - } -} - -// sandboxPid looks for the sandbox process inside the process tree starting -// from "pid". It returns 0 and no error if no sandbox process is found. It -// returns error if anything failed. -func sandboxPid(pid int) (int, error) { - cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid)) - buf := &bytes.Buffer{} - cmd.Stdout = buf - if err := cmd.Start(); err != nil { - return 0, err - } - ps, err := cmd.Process.Wait() - if err != nil { - return 0, err - } - if ps.ExitCode() == 1 { - // pgrep returns 1 when no process is found. - return 0, nil - } - - var children []int - for _, line := range strings.Split(buf.String(), "\n") { - if len(line) == 0 { - continue - } - child, err := strconv.Atoi(line) - if err != nil { - return 0, err - } - - cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline")) - if err != nil { - if os.IsNotExist(err) { - // Raced with process exit. - continue - } - return 0, err - } - args := strings.SplitN(string(cmdline), "\x00", 2) - if len(args) == 0 { - return 0, fmt.Errorf("malformed cmdline file: %q", cmdline) - } - // The sandbox process has the first argument set to "runsc-sandbox". - if args[0] == "runsc-sandbox" { - return child, nil - } - - children = append(children, child) - } - - // Sandbox process wasn't found, try another level down. - for _, pid := range children { - sand, err := sandboxPid(pid) - if err != nil { - return 0, err - } - if sand != 0 { - return sand, nil - } - // Not found, continue the search. - } - return 0, nil -} diff --git a/test/runner/BUILD b/test/runner/BUILD deleted file mode 100644 index 582d2946d..000000000 --- a/test/runner/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -load("//tools:defs.bzl", "bzl_library", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "runner", - testonly = 1, - srcs = ["runner.go"], - data = [ - "//runsc", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/log", - "//pkg/test/testutil", - "//runsc/specutils", - "//test/runner/gtest", - "//test/uds", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -bzl_library( - name = "defs_bzl", - srcs = ["defs.bzl"], - visibility = ["//visibility:private"], -) diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl deleted file mode 100644 index 829247657..000000000 --- a/test/runner/defs.bzl +++ /dev/null @@ -1,272 +0,0 @@ -"""Defines a rule for syscall test targets.""" - -load("//tools:defs.bzl", "default_platform", "platforms") - -def _runner_test_impl(ctx): - # Generate a runner binary. - runner = ctx.actions.declare_file("%s-runner" % ctx.label.name) - runner_content = "\n".join([ - "#!/bin/bash", - "set -euf -x -o pipefail", - "if [[ -n \"${TEST_UNDECLARED_OUTPUTS_DIR}\" ]]; then", - " mkdir -p \"${TEST_UNDECLARED_OUTPUTS_DIR}\"", - " chmod a+rwx \"${TEST_UNDECLARED_OUTPUTS_DIR}\"", - "fi", - "exec %s %s \"$@\" %s\n" % ( - ctx.files.runner[0].short_path, - " ".join(ctx.attr.runner_args), - ctx.files.test[0].short_path, - ), - ]) - ctx.actions.write(runner, runner_content, is_executable = True) - - # Return with all transitive files. - runfiles = ctx.runfiles( - transitive_files = depset(transitive = [ - target.data_runfiles.files - for target in (ctx.attr.runner, ctx.attr.test) - if hasattr(target, "data_runfiles") - ]), - files = ctx.files.runner + ctx.files.test, - collect_default = True, - collect_data = True, - ) - return [DefaultInfo(executable = runner, runfiles = runfiles)] - -_runner_test = rule( - attrs = { - "runner": attr.label( - default = "//test/runner:runner", - ), - "test": attr.label( - mandatory = True, - ), - "runner_args": attr.string_list(), - "data": attr.label_list( - allow_files = True, - ), - }, - test = True, - implementation = _runner_test_impl, -) - -def _syscall_test( - test, - platform, - use_tmpfs, - tags, - debug, - network = "none", - file_access = "exclusive", - overlay = False, - add_uds_tree = False, - vfs2 = False, - fuse = False, - **kwargs): - # Prepend "runsc" to non-native platform names. - full_platform = platform if platform == "native" else "runsc_" + platform - - # Name the test appropriately. - name = test.split(":")[1] + "_" + full_platform - if file_access == "shared": - name += "_shared" - if overlay: - name += "_overlay" - if vfs2: - name += "_vfs2" - if fuse: - name += "_fuse" - if network != "none": - name += "_" + network + "net" - - # Apply all tags. - if tags == None: - tags = [] - - # Add the full_platform and file access in a tag to make it easier to run - # all the tests on a specific flavor. Use --test_tag_filters=ptrace,file_shared. - tags += [full_platform, "file_" + file_access] - - # Hash this target into one of 15 buckets. This can be used to - # randomly split targets between different workflows. - hash15 = hash(native.package_name() + name) % 15 - tags.append("hash15:" + str(hash15)) - - # TODO(b/139838000): Tests using hostinet must be disabled on Guitar until - # we figure out how to request ipv4 sockets on Guitar machines. - if network == "host": - tags.append("noguitar") - - # Disable off-host networking. - tags.append("requires-net:loopback") - tags.append("requires-net:ipv4") - tags.append("block-network") - - # gotsan makes sense only if tests are running in gVisor. - if platform == "native": - tags.append("nogotsan") - - runner_args = [ - # Arguments are passed directly to runner binary. - "--platform=" + platform, - "--network=" + network, - "--use-tmpfs=" + str(use_tmpfs), - "--file-access=" + file_access, - "--overlay=" + str(overlay), - "--add-uds-tree=" + str(add_uds_tree), - "--vfs2=" + str(vfs2), - "--fuse=" + str(fuse), - "--strace=" + str(debug), - "--debug=" + str(debug), - ] - - # Call the rule above. - _runner_test( - name = name, - test = test, - runner_args = runner_args, - tags = tags, - **kwargs - ) - -def syscall_test( - test, - use_tmpfs = False, - add_overlay = False, - add_uds_tree = False, - add_hostinet = False, - vfs2 = True, - fuse = False, - debug = True, - tags = None, - **kwargs): - """syscall_test is a macro that will create targets for all platforms. - - Args: - test: the test target. - use_tmpfs: use tmpfs in the defined tests. - add_overlay: add an overlay test. - add_uds_tree: add a UDS test. - add_hostinet: add a hostinet test. - vfs2: enable VFS2 support. - fuse: enable FUSE support. - debug: enable debug output. - tags: starting test tags. - **kwargs: additional test arguments. - """ - if not tags: - tags = [] - - vfs2_tags = list(tags) - if vfs2: - # Add tag to easily run VFS2 tests with --test_tag_filters=vfs2 - vfs2_tags.append("vfs2") - if fuse: - vfs2_tags.append("fuse") - - else: - # Don't automatically run tests tests not yet passing. - vfs2_tags.append("manual") - vfs2_tags.append("noguitar") - vfs2_tags.append("notap") - - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + vfs2_tags, - debug = debug, - vfs2 = True, - fuse = fuse, - **kwargs - ) - if fuse: - # Only generate *_vfs2_fuse target if fuse parameter is enabled. - return - - _syscall_test( - test = test, - platform = "native", - use_tmpfs = False, - add_uds_tree = add_uds_tree, - tags = list(tags), - debug = debug, - **kwargs - ) - - for (platform, platform_tags) in platforms.items(): - _syscall_test( - test = test, - platform = platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platform_tags + tags, - debug = debug, - **kwargs - ) - - if add_overlay: - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + tags, - debug = debug, - overlay = True, - **kwargs - ) - - # TODO(gvisor.dev/issue/4407): Remove tags to enable VFS2 overlay tests. - overlay_vfs2_tags = list(vfs2_tags) - overlay_vfs2_tags.append("manual") - overlay_vfs2_tags.append("noguitar") - overlay_vfs2_tags.append("notap") - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + overlay_vfs2_tags, - debug = debug, - overlay = True, - vfs2 = True, - **kwargs - ) - - if add_hostinet: - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - network = "host", - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + tags, - debug = debug, - **kwargs - ) - - if not use_tmpfs: - # Also test shared gofer access. - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + tags, - debug = debug, - file_access = "shared", - **kwargs - ) - _syscall_test( - test = test, - platform = default_platform, - use_tmpfs = use_tmpfs, - add_uds_tree = add_uds_tree, - tags = platforms[default_platform] + vfs2_tags, - debug = debug, - file_access = "shared", - vfs2 = True, - **kwargs - ) diff --git a/test/runner/gtest/BUILD b/test/runner/gtest/BUILD deleted file mode 100644 index de4b2727c..000000000 --- a/test/runner/gtest/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "gtest", - srcs = ["gtest.go"], - visibility = ["//:sandbox"], -) diff --git a/test/runner/gtest/gtest.go b/test/runner/gtest/gtest.go deleted file mode 100644 index 2ad5f58ef..000000000 --- a/test/runner/gtest/gtest.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package gtest contains helpers for running google-test tests from Go. -package gtest - -import ( - "fmt" - "os/exec" - "strings" -) - -var ( - // listTestFlag is the flag that will list tests in gtest binaries. - listTestFlag = "--gtest_list_tests" - - // filterTestFlag is the flag that will filter tests in gtest binaries. - filterTestFlag = "--gtest_filter" - - // listBechmarkFlag is the flag that will list benchmarks in gtest binaries. - listBenchmarkFlag = "--benchmark_list_tests" - - // filterBenchmarkFlag is the flag that will run specified benchmarks. - filterBenchmarkFlag = "--benchmark_filter" -) - -// BuildTestArgs builds arguments to be passed to the test binary to execute -// only the test cases in `indices`. -func BuildTestArgs(indices []int, testCases []TestCase) []string { - var testFilter, benchFilter string - for _, tci := range indices { - tc := testCases[tci] - if tc.all { - // No argument will make all tests run. - return nil - } - if tc.benchmark { - if len(benchFilter) > 0 { - benchFilter += "|" - } - benchFilter += "^" + tc.Name + "$" - } else { - if len(testFilter) > 0 { - testFilter += ":" - } - testFilter += tc.FullName() - } - } - - var args []string - if len(testFilter) > 0 { - args = append(args, fmt.Sprintf("%s=%s", filterTestFlag, testFilter)) - } - if len(benchFilter) > 0 { - args = append(args, fmt.Sprintf("%s=%s", filterBenchmarkFlag, benchFilter)) - } - return args -} - -// TestCase is a single gtest test case. -type TestCase struct { - // Suite is the suite for this test. - Suite string - - // Name is the name of this individual test. - Name string - - // all indicates that this will run without flags. This takes - // precendence over benchmark below. - all bool - - // benchmark indicates that this is a benchmark. In this case, the - // suite will be empty, and we will use the appropriate test and - // benchmark flags. - benchmark bool -} - -// FullName returns the name of the test including the suite. It is suitable to -// pass to "-gtest_filter". -func (tc TestCase) FullName() string { - return fmt.Sprintf("%s.%s", tc.Suite, tc.Name) -} - -// ParseTestCases calls a gtest test binary to list its test and returns a -// slice with the name and suite of each test. -// -// If benchmarks is true, then benchmarks will be included in the list of test -// cases provided. Note that this requires the binary to support the -// benchmarks_list_tests flag. -func ParseTestCases(testBin string, benchmarks bool, extraArgs ...string) ([]TestCase, error) { - // Run to extract test cases. - args := append([]string{listTestFlag}, extraArgs...) - cmd := exec.Command(testBin, args...) - out, err := cmd.Output() - if err != nil { - // We failed to list tests with the given flags. Just - // return something that will run the binary with no - // flags, which should execute all tests. - fmt.Printf("failed to get test list: %v\n", err) - return []TestCase{ - { - Suite: "Default", - Name: "All", - all: true, - }, - }, nil - } - - // Parse test output. - var t []TestCase - var suite string - for _, line := range strings.Split(string(out), "\n") { - // Strip comments. - line = strings.Split(line, "#")[0] - - // New suite? - if !strings.HasPrefix(line, " ") { - suite = strings.TrimSuffix(strings.TrimSpace(line), ".") - continue - } - - // Individual test. - name := strings.TrimSpace(line) - - // Do we have a suite yet? - if suite == "" { - return nil, fmt.Errorf("test without a suite: %v", name) - } - - // Add this individual test. - t = append(t, TestCase{ - Suite: suite, - Name: name, - }) - } - - // Finished? - if !benchmarks { - return t, nil - } - - // Run again to extract benchmarks. - args = append([]string{listBenchmarkFlag}, extraArgs...) - cmd = exec.Command(testBin, args...) - out, err = cmd.Output() - if err != nil { - // We were able to enumerate tests above, but not benchmarks? - // We requested them, so we return an error in this case. - exitErr, ok := err.(*exec.ExitError) - if !ok { - return nil, fmt.Errorf("could not enumerate gtest benchmarks: %v", err) - } - return nil, fmt.Errorf("could not enumerate gtest benchmarks: %v\nstderr\n%s", err, exitErr.Stderr) - } - - benches := strings.Trim(string(out), "\n") - if len(benches) == 0 { - return t, nil - } - - // Parse benchmark output. - for _, line := range strings.Split(benches, "\n") { - // Strip comments. - line = strings.Split(line, "#")[0] - - // Single benchmark. - name := strings.TrimSpace(line) - - // Add the single benchmark. - t = append(t, TestCase{ - Suite: "Benchmarks", - Name: name, - benchmark: true, - }) - } - return t, nil -} diff --git a/test/runner/runner.go b/test/runner/runner.go deleted file mode 100644 index a8a134fe2..000000000 --- a/test/runner/runner.go +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Binary syscall_test_runner runs the syscall test suites in gVisor -// containers and on the host platform. -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "os" - "os/exec" - "os/signal" - "path/filepath" - "strings" - "syscall" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/syndtr/gocapability/capability" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/specutils" - "gvisor.dev/gvisor/test/runner/gtest" - "gvisor.dev/gvisor/test/uds" -) - -var ( - debug = flag.Bool("debug", false, "enable debug logs") - strace = flag.Bool("strace", false, "enable strace logs") - platform = flag.String("platform", "ptrace", "platform to run on") - network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") - useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") - fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") - overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") - vfs2 = flag.Bool("vfs2", false, "enable VFS2") - fuse = flag.Bool("fuse", false, "enable FUSE") - runscPath = flag.String("runsc", "", "path to runsc binary") - - addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests") - // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and - // set to true as the default for the test runner. - leakCheck = flag.Bool("leak-check", false, "check for reference leaks") -) - -func main() { - flag.Parse() - if flag.NArg() != 1 { - fatalf("test must be provided") - } - - log.SetLevel(log.Info) - if *debug { - log.SetLevel(log.Debug) - } - - if *platform != "native" && *runscPath == "" { - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } - *runscPath = specutils.ExePath - } - - // Make sure stdout and stderr are opened with O_APPEND, otherwise logs - // from outside the sandbox can (and will) stomp on logs from inside - // the sandbox. - for _, f := range []*os.File{os.Stdout, os.Stderr} { - flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0) - if err != nil { - fatalf("error getting file flags for %v: %v", f, err) - } - if flags&unix.O_APPEND == 0 { - flags |= unix.O_APPEND - if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil { - fatalf("error setting file flags for %v: %v", f, err) - } - } - } - - // Resolve the absolute path for the binary. - testBin, err := filepath.Abs(flag.Args()[0]) - if err != nil { - fatalf("Abs(%q) failed: %v", flag.Args()[0], err) - } - - // Get all test cases in each binary. - testCases, err := gtest.ParseTestCases(testBin, true) - if err != nil { - fatalf("ParseTestCases(%q) failed: %v", testBin, err) - } - - // Get subset of tests corresponding to shard. - indices, err := testutil.TestIndicesForShard(len(testCases)) - if err != nil { - fatalf("TestsForShard() failed: %v", err) - } - if len(indices) == 0 { - log.Warningf("No tests to run in this shard") - return - } - args := gtest.BuildTestArgs(indices, testCases) - - switch *platform { - case "native": - if err := runTestCaseNative(testBin, args); err != nil { - fatalf(err.Error()) - } - default: - if err := runTestCaseRunsc(testBin, args); err != nil { - fatalf(err.Error()) - } - } -} - -// runTestCaseNative runs the test case directly on the host machine. -func runTestCaseNative(testBin string, args []string) error { - // These tests might be running in parallel, so make sure they have a - // unique test temp dir. - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") - if err != nil { - return fmt.Errorf("could not create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - // Replace TEST_TMPDIR in the current environment with something - // unique. - env := os.Environ() - newEnvVar := "TEST_TMPDIR=" + tmpDir - var found bool - for i, kv := range env { - if strings.HasPrefix(kv, "TEST_TMPDIR=") { - env[i] = newEnvVar - found = true - break - } - } - if !found { - env = append(env, newEnvVar) - } - // Remove shard env variables so that the gunit binary does not try to - // interpret them. - env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS") - - if *addUDSTree { - socketDir, cleanup, err := uds.CreateSocketTree("/tmp") - if err != nil { - return fmt.Errorf("failed to create socket tree: %v", err) - } - defer cleanup() - - env = append(env, "TEST_UDS_TREE="+socketDir) - // On Linux, the concept of "attach" location doesn't exist. - // Just pass the same path to make these test identical. - env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir) - } - - cmd := exec.Command(testBin, args...) - cmd.Env = env - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.SysProcAttr = &unix.SysProcAttr{} - - if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { - cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS - } - - if specutils.HasCapabilities(capability.CAP_NET_ADMIN) { - cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET - } - - if err := cmd.Run(); err != nil { - ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus) - return fmt.Errorf("test exited with status %d, want 0", ws.ExitStatus()) - } - return nil -} - -// runRunsc runs spec in runsc in a standard test configuration. -// -// runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR. -// -// Returns an error if the sandboxed application exits non-zero. -func runRunsc(spec *specs.Spec) error { - bundleDir, cleanup, err := testutil.SetupBundleDir(spec) - if err != nil { - return fmt.Errorf("SetupBundleDir failed: %v", err) - } - defer cleanup() - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - return fmt.Errorf("SetupRootDir failed: %v", err) - } - defer cleanup() - - id := testutil.RandomContainerID() - log.Infof("Running test in container %q", id) - specutils.LogSpec(spec) - - args := []string{ - "-root", rootDir, - "-network", *network, - "-log-format=text", - "-TESTONLY-unsafe-nonroot=true", - "-net-raw=true", - fmt.Sprintf("-panic-signal=%d", unix.SIGTERM), - "-watchdog-action=panic", - "-platform", *platform, - "-file-access", *fileAccess, - } - if *overlay { - args = append(args, "-overlay") - } - if *vfs2 { - args = append(args, "-vfs2") - if *fuse { - args = append(args, "-fuse") - } - } - if *debug { - args = append(args, "-debug", "-log-packets=true") - } - if *strace { - args = append(args, "-strace") - } - if *addUDSTree { - args = append(args, "-fsgofer-host-uds") - } - if *leakCheck { - args = append(args, "-ref-leak-mode=log-names") - } - - testLogDir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR") - if len(testLogDir) > 0 { - debugLogDir, err := ioutil.TempDir(testLogDir, "runsc") - if err != nil { - return fmt.Errorf("could not create temp dir: %v", err) - } - debugLogDir += "/" - log.Infof("runsc logs: %s", debugLogDir) - args = append(args, "-debug-log", debugLogDir) - - // Default -log sends messages to stderr which makes reading the test log - // difficult. Instead, drop them when debug log is enabled given it's a - // better place for these messages. - args = append(args, "-log=/dev/null") - } - - // Current process doesn't have CAP_SYS_ADMIN, create user namespace and run - // as root inside that namespace to get it. - rArgs := append(args, "run", "--bundle", bundleDir, id) - cmd := exec.Command(*runscPath, rArgs...) - cmd.SysProcAttr = &unix.SysProcAttr{ - Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNS, - // Set current user/group as root inside the namespace. - UidMappings: []syscall.SysProcIDMap{ - {ContainerID: 0, HostID: os.Getuid(), Size: 1}, - }, - GidMappings: []syscall.SysProcIDMap{ - {ContainerID: 0, HostID: os.Getgid(), Size: 1}, - }, - GidMappingsEnableSetgroups: false, - Credential: &syscall.Credential{ - Uid: 0, - Gid: 0, - }, - } - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - sig := make(chan os.Signal, 1) - defer close(sig) - signal.Notify(sig, unix.SIGTERM) - defer signal.Stop(sig) - go func() { - s, ok := <-sig - if !ok { - return - } - log.Warningf("Got signal: %v", s) - done := make(chan bool, 1) - dArgs := append([]string{}, args...) - dArgs = append(dArgs, "-alsologtostderr=true", "debug", "--stacks", id) - go func(dArgs []string) { - debug := exec.Command(*runscPath, dArgs...) - debug.Stdout = os.Stdout - debug.Stderr = os.Stderr - debug.Run() - done <- true - }(dArgs) - - timeout := time.After(3 * time.Second) - select { - case <-timeout: - log.Infof("runsc debug --stacks is timeouted") - case <-done: - } - - log.Warningf("Send SIGTERM to the sandbox process") - dArgs = append(args, "debug", - fmt.Sprintf("--signal=%d", unix.SIGTERM), - id) - signal := exec.Command(*runscPath, dArgs...) - signal.Stdout = os.Stdout - signal.Stderr = os.Stderr - signal.Run() - }() - - err = cmd.Run() - if err == nil && len(testLogDir) > 0 { - // If the test passed, then we erase the log directory. This speeds up - // uploading logs in continuous integration & saves on disk space. - _ = os.RemoveAll(testLogDir) - } - - return err -} - -// setupUDSTree updates the spec to expose a UDS tree for gofer socket testing. -func setupUDSTree(spec *specs.Spec) (cleanup func(), err error) { - socketDir, cleanup, err := uds.CreateSocketTree("/tmp") - if err != nil { - return nil, fmt.Errorf("failed to create socket tree: %v", err) - } - - // Standard access to entire tree. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets", - Source: socketDir, - Type: "bind", - }) - - // Individial attach points for each socket to test mounts that attach - // directly to the sockets. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets-attach/stream/echo", - Source: filepath.Join(socketDir, "stream/echo"), - Type: "bind", - }) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets-attach/stream/nonlistening", - Source: filepath.Join(socketDir, "stream/nonlistening"), - Type: "bind", - }) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets-attach/seqpacket/echo", - Source: filepath.Join(socketDir, "seqpacket/echo"), - Type: "bind", - }) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets-attach/seqpacket/nonlistening", - Source: filepath.Join(socketDir, "seqpacket/nonlistening"), - Type: "bind", - }) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp/sockets-attach/dgram/null", - Source: filepath.Join(socketDir, "dgram/null"), - Type: "bind", - }) - - spec.Process.Env = append(spec.Process.Env, "TEST_UDS_TREE=/tmp/sockets") - spec.Process.Env = append(spec.Process.Env, "TEST_UDS_ATTACH_TREE=/tmp/sockets-attach") - - return cleanup, nil -} - -// runsTestCaseRunsc runs the test case in runsc. -func runTestCaseRunsc(testBin string, args []string) error { - // Run a new container with the test executable and filter for the - // given test suite and name. - spec := testutil.NewSpecWithArgs(append([]string{testBin}, args...)...) - - // Mark the root as writeable, as some tests attempt to - // write to the rootfs, and expect EACCES, not EROFS. - spec.Root.Readonly = false - - // Test spec comes with pre-defined mounts that we don't want. Reset it. - spec.Mounts = nil - testTmpDir := "/tmp" - if *useTmpfs { - // Forces '/tmp' to be mounted as tmpfs, otherwise test that rely on - // features only available in gVisor's internal tmpfs may fail. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp", - Type: "tmpfs", - }) - } else { - // Use a gofer-backed directory as '/tmp'. - // - // Tests might be running in parallel, so make sure each has a - // unique test temp dir. - // - // Some tests (e.g., sticky) access this mount from other - // users, so make sure it is world-accessible. - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") - if err != nil { - return fmt.Errorf("could not create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - if err := os.Chmod(tmpDir, 0777); err != nil { - return fmt.Errorf("could not chmod temp dir: %v", err) - } - - // "/tmp" is not replaced with a tmpfs mount inside the sandbox - // when it's not empty. This ensures that testTmpDir uses gofer - // in exclusive mode. - testTmpDir = tmpDir - if *fileAccess == "shared" { - // All external mounts except the root mount are shared. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Type: "bind", - Destination: "/tmp", - Source: tmpDir, - }) - testTmpDir = "/tmp" - } - } - - // Set environment variables that indicate we are running in gVisor with - // the given platform, network, and filesystem stack. - env := []string{"TEST_ON_GVISOR=" + *platform, "GVISOR_NETWORK=" + *network} - env = append(env, os.Environ()...) - const vfsVar = "GVISOR_VFS" - if *vfs2 { - env = append(env, vfsVar+"=VFS2") - const fuseVar = "FUSE_ENABLED" - if *fuse { - env = append(env, fuseVar+"=TRUE") - } else { - env = append(env, fuseVar+"=FALSE") - } - } else { - env = append(env, vfsVar+"=VFS1") - } - - // Remove shard env variables so that the gunit binary does not try to - // interpret them. - env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS") - - // Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to - // be backed by tmpfs. - env = filterEnv(env, "TEST_TMPDIR") - env = append(env, fmt.Sprintf("TEST_TMPDIR=%s", testTmpDir)) - - spec.Process.Env = env - - if *addUDSTree { - cleanup, err := setupUDSTree(spec) - if err != nil { - return fmt.Errorf("error creating UDS tree: %v", err) - } - defer cleanup() - } - - if err := runRunsc(spec); err != nil { - return fmt.Errorf("test failed with error %v, want nil", err) - } - return nil -} - -// filterEnv returns an environment with the excluded variables removed. -func filterEnv(env []string, exclude ...string) []string { - var out []string - for _, kv := range env { - ok := true - for _, k := range exclude { - if strings.HasPrefix(kv, k+"=") { - ok = false - break - } - } - if ok { - out = append(out, kv) - } - } - return out -} - -func fatalf(s string, args ...interface{}) { - fmt.Fprintf(os.Stderr, s+"\n", args...) - os.Exit(1) -} diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD deleted file mode 100644 index 510ffe013..000000000 --- a/test/runtimes/BUILD +++ /dev/null @@ -1,46 +0,0 @@ -load("//tools:defs.bzl", "bzl_library", "more_shards", "most_shards") -load("//test/runtimes:defs.bzl", "runtime_test") - -package(licenses = ["notice"]) - -runtime_test( - name = "go1.12", - exclude_file = "exclude/go1.12.csv", - lang = "go", - shard_count = more_shards, -) - -runtime_test( - name = "java11", - batch = 100, - exclude_file = "exclude/java11.csv", - lang = "java", - shard_count = most_shards, -) - -runtime_test( - name = "nodejs12.4.0", - exclude_file = "exclude/nodejs12.4.0.csv", - lang = "nodejs", - shard_count = most_shards, -) - -runtime_test( - name = "php7.3.6", - exclude_file = "exclude/php7.3.6.csv", - lang = "php", - shard_count = more_shards, -) - -runtime_test( - name = "python3.7.3", - exclude_file = "exclude/python3.7.3.csv", - lang = "python", - shard_count = more_shards, -) - -bzl_library( - name = "defs_bzl", - srcs = ["defs.bzl"], - visibility = ["//visibility:private"], -) diff --git a/test/runtimes/README.md b/test/runtimes/README.md deleted file mode 100644 index 9dda1a728..000000000 --- a/test/runtimes/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# gVisor Runtime Tests - -App Engine uses gvisor to sandbox application containers. The runtime tests aim -to test `runsc` compatibility with these -[standard runtimes](https://cloud.google.com/appengine/docs/standard/runtimes). -The test itself runs the language-defined tests inside the sandboxed standard -runtime container. - -Note: [Ruby runtime](https://cloud.google.com/appengine/docs/standard/ruby) is -currently in beta mode and so we do not run tests for it yet. - -### Testing Locally - -To run runtime tests individually from a given runtime, use the following table. - -Language | Version | Download Image | Run Test(s) --------- | ------- | ------------------------------------------- | ----------- -Go | 1.12 | `make -C images load-runtimes_go1.12` | If the test name ends with `.go`, it is an on-disk test: <br> `docker run --runtime=runsc -it gvisor.dev/images/runtimes/go1.12 ( cd /usr/local/go/test ; go run run.go -v -- <TEST_NAME>... )` <br> Otherwise it is a tool test: <br> `docker run --runtime=runsc -it gvisor.dev/images/runtimes/go1.12 go tool dist test -v -no-rebuild ^TEST1$\|^TEST2$...` -Java | 11 | `make -C images load-runtimes_java11` | `docker run --runtime=runsc -it gvisor.dev/images/runtimes/java11 jtreg -agentvm -dir:/root/test/jdk -noreport -timeoutFactor:20 -verbose:summary <TEST_NAME>...` -NodeJS | 12.4.0 | `make -C images load-runtimes_nodejs12.4.0` | `docker run --runtime=runsc -it gvisor.dev/images/runtimes/nodejs12.4.0 python tools/test.py --timeout=180 <TEST_NAME>...` -Php | 7.3.6 | `make -C images load-runtimes_php7.3.6` | `docker run --runtime=runsc -it gvisor.dev/images/runtimes/php7.3.6 make test "TESTS=<TEST_NAME>..."` -Python | 3.7.3 | `make -C images load-runtimes_python3.7.3` | `docker run --runtime=runsc -it gvisor.dev/images/runtimes/python3.7.3 ./python -m test <TEST_NAME>...` - -To run an entire runtime test locally, use the following table. - -Note: java runtime test take 1+ hours with 16 cores. - -Language | Version | Running the test suite --------- | ------- | ---------------------------------------- -Go | 1.12 | `make go1.12-runtime-tests{_vfs2}` -Java | 11 | `make java11-runtime-tests{_vfs2}` -NodeJS | 12.4.0 | `make nodejs12.4.0-runtime-tests{_vfs2}` -Php | 7.3.6 | `make php7.3.6-runtime-tests{_vfs2}` -Python | 3.7.3 | `make python3.7.3-runtime-tests{_vfs2}` - -#### Clean Up - -Sometimes when runtime tests fail or when the testing container itself crashes -unexpectedly, the containers are not removed or sometimes do not even exit. This -can cause some docker commands like `docker system prune` to hang forever. - -Here are some helpful commands (should be executed in order): - -```bash -docker ps -a # Lists all docker processes; useful when investigating hanging containers. -docker kill $(docker ps -a -q) # Kills all running containers. -docker rm $(docker ps -a -q) # Removes all exited containers. -docker system prune # Remove unused data. -``` - -### Testing Infrastructure - -There are 3 components to this tests infrastructure: - -- [`runner`](runner) - This is the test entrypoint. This is the binary is - invoked by `bazel test`. The runner spawns the target runtime container - using `runsc` and then copies over the `proctor` binary into the container. -- [`proctor`](proctor) - This binary acts as our agent inside the container - which communicates with the runner and actually executes tests. -- [`exclude`](exclude) - Holds a CSV file for each language runtime containing - the full path of tests that should be excluded from running along with a - reason for exclusion. diff --git a/test/runtimes/defs.bzl b/test/runtimes/defs.bzl deleted file mode 100644 index 702522d86..000000000 --- a/test/runtimes/defs.bzl +++ /dev/null @@ -1,90 +0,0 @@ -"""Defines a rule for runtime test targets.""" - -load("//tools:defs.bzl", "go_test") - -def _runtime_test_impl(ctx): - # Construct arguments. - args = [ - "--lang", - ctx.attr.lang, - "--image", - ctx.attr.image, - "--batch", - str(ctx.attr.batch), - ] - if ctx.attr.exclude_file: - args += [ - "--exclude_file", - ctx.files.exclude_file[0].short_path, - ] - - # Build a runner. - runner = ctx.actions.declare_file("%s-executer" % ctx.label.name) - runner_content = "\n".join([ - "#!/bin/bash", - "%s %s $@\n" % (ctx.files._runner[0].short_path, " ".join(args)), - ]) - ctx.actions.write(runner, runner_content, is_executable = True) - - # Return the runner. - return [DefaultInfo( - executable = runner, - runfiles = ctx.runfiles( - files = ctx.files._runner + ctx.files.exclude_file + ctx.files._proctor, - collect_default = True, - collect_data = True, - ), - )] - -_runtime_test = rule( - implementation = _runtime_test_impl, - attrs = { - "image": attr.string( - mandatory = False, - ), - "lang": attr.string( - mandatory = True, - ), - "exclude_file": attr.label( - mandatory = False, - allow_single_file = True, - ), - "batch": attr.int( - default = 50, - mandatory = False, - ), - "_runner": attr.label( - default = "//test/runtimes/runner:runner", - executable = True, - cfg = "target", - ), - "_proctor": attr.label( - default = "//test/runtimes/proctor:proctor", - executable = True, - cfg = "target", - ), - }, - test = True, -) - -def runtime_test(name, **kwargs): - _runtime_test( - name = name, - image = name, # Resolved as images/runtimes/%s. - tags = [ - "local", - "manual", - ], - size = "enormous", - **kwargs - ) - -def exclude_test(name, exclude_file): - """Test that a exclude file parses correctly.""" - go_test( - name = name + "_exclude_test", - library = ":runner", - srcs = ["exclude_test.go"], - args = ["--exclude_file", "test/runtimes/" + exclude_file], - data = [exclude_file], - ) diff --git a/test/runtimes/exclude/go1.12.csv b/test/runtimes/exclude/go1.12.csv deleted file mode 100644 index 81e02cf64..000000000 --- a/test/runtimes/exclude/go1.12.csv +++ /dev/null @@ -1,13 +0,0 @@ -test name,bug id,comment -cgo_errors,,FLAKY -cgo_test,,FLAKY -go_test:cmd/go,,FLAKY -go_test:net,b/162473575,setsockopt: protocol not available. -go_test:os,b/118780122,we have a pollable filesystem but that's a surprise -go_test:os/signal,b/118780860,/dev/pts not properly supported. Also being tracked in b/29356795. -go_test:runtime,b/118782341,sigtrap not reported or caught or something. Also being tracked in b/33003106. -go_test:syscall,b/118781998,bad bytes -- bad mem addr; FcntlFlock(F_GETLK) not supported. -runtime:cpu124,b/118778254,segmentation fault -test:0_1,,FLAKY -testcarchive,b/118782924,no sigpipe -testshared,,FLAKY diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv deleted file mode 100644 index d069d5a2e..000000000 --- a/test/runtimes/exclude/java11.csv +++ /dev/null @@ -1,220 +0,0 @@ -test name,bug id,comment -com/sun/crypto/provider/Cipher/PBE/PKCS12Cipher.java,,Fails in Docker -com/sun/jdi/InvokeHangTest.java,https://bugs.openjdk.java.net/browse/JDK-8218463, -com/sun/jdi/NashornPopFrameTest.java,, -com/sun/jdi/OnJcmdTest.java,b/180542784, -com/sun/jdi/ProcessAttachTest.java,, -com/sun/management/HotSpotDiagnosticMXBean/CheckOrigin.java,,Fails in Docker -com/sun/management/OperatingSystemMXBean/GetCommittedVirtualMemorySize.java,, -com/sun/management/ThreadMXBean/ThreadCpuTimeArray.java,,Test assumes high CPU clock precision -com/sun/management/UnixOperatingSystemMXBean/GetMaxFileDescriptorCount.sh,, -com/sun/tools/attach/AttachSelf.java,, -com/sun/tools/attach/BasicTests.java,, -com/sun/tools/attach/PermissionTest.java,, -com/sun/tools/attach/StartManagementAgent.java,, -com/sun/tools/attach/TempDirTest.java,, -com/sun/tools/attach/modules/Driver.java,, -java/lang/Character/CheckScript.java,,Fails in Docker -java/lang/Character/CheckUnicode.java,,Fails in Docker -java/lang/Class/GetPackageBootLoaderChildLayer.java,, -java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java,,Fails in Docker -java/lang/module/ModuleDescriptorTest.java,, -java/lang/String/nativeEncoding/StringPlatformChars.java,, -java/net/CookieHandler/B6791927.java,,java.lang.RuntimeException: Expiration date shouldn't be 0 -java/net/DatagramSocket/SetGetReceiveBufferSize.java,b/180507650, -java/net/httpclient/websocket/WebSocketProxyTest.java,,Times out on runc too -java/net/ipv6tests/TcpTest.java,,java.net.ConnectException: Connection timed out (Connection timed out) -java/net/ipv6tests/UdpTest.java,,Times out -java/net/Inet6Address/B6558853.java,,Times out -java/net/InetAddress/CheckJNI.java,,java.net.ConnectException: Connection timed out (Connection timed out) -java/net/InterfaceAddress/NetworkPrefixLength.java,b/78507103, -java/net/MulticastSocket/B6425815.java,,java.net.SocketException: Protocol not available (Error getting socket option) -java/net/MulticastSocket/B6427403.java,,java.net.SocketException: Protocol not available -java/net/MulticastSocket/MulticastTTL.java,, -java/net/MulticastSocket/NetworkInterfaceEmptyGetInetAddressesTest.java,,java.net.SocketException: Protocol not available (Error getting socket option) -java/net/MulticastSocket/NoLoopbackPackets.java,,java.net.SocketException: Protocol not available -java/net/MulticastSocket/Promiscuous.java,, -java/net/MulticastSocket/SetLoopbackMode.java,, -java/net/MulticastSocket/SetTTLAndGetTTL.java,, -java/net/MulticastSocket/Test.java,, -java/net/MulticastSocket/TestDefaults.java,, -java/net/MulticastSocket/TimeToLive.java,, -java/net/NetworkInterface/NetworkInterfaceStreamTest.java,, -java/net/Socket/LinkLocal.java,,java.net.SocketTimeoutException: Receive timed out -java/net/Socket/SetSoLinger.java,b/78527327,SO_LINGER is not yet supported -java/net/Socket/UrgentDataTest.java,b/111515323, -java/net/SocketOption/OptionsTest.java,,Fails in Docker -java/net/SocketPermission/SocketPermissionTest.java,, -java/net/URLConnection/6212146/TestDriver.java,,Fails in Docker -java/net/httpclient/RequestBuilderTest.java,,Fails in Docker -java/nio/channels/DatagramChannel/BasicMulticastTests.java,, -java/nio/channels/DatagramChannel/SocketOptionTests.java,,java.net.SocketException: Invalid argument -java/nio/channels/DatagramChannel/UseDGWithIPv6.java,, -java/nio/channels/FileChannel/directio/DirectIOTest.java,,Fails in Docker -java/nio/channels/FileChannel/directio/PwriteDirect.java,,java.io.IOException: Invalid argument -java/nio/channels/Selector/OutOfBand.java,, -java/nio/channels/Selector/SelectWithConsumer.java,,Flaky -java/nio/channels/ServerSocketChannel/SocketOptionTests.java,, -java/nio/channels/SocketChannel/LingerOnClose.java,, -java/nio/channels/SocketChannel/SocketOptionTests.java,b/77965901, -java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java,,Fails in Docker -java/rmi/activation/Activatable/extLoadedImpl/ext.sh,, -java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java,, -java/security/cert/PolicyNode/GetPolicyQualifiers.java,b/170263154,Kokoro executor cert expired -java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker -java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker -java/util/Calendar/JapaneseEraNameTest.java,, -java/util/Currency/CurrencyTest.java,,Fails in Docker -java/util/Currency/ValidateISO4217.java,,Fails in Docker -java/util/EnumSet/BogusEnumSet.java,,"java.io.InvalidClassException: java.util.EnumSet; local class incompatible: stream classdesc serialVersionUID = -2409567991088730183, local class serialVersionUID = 1009687484059888093" -java/util/Locale/Bug8040211.java,,java.lang.RuntimeException: Failed. -java/util/Locale/LSRDataTest.java,, -java/util/Properties/CompatibilityTest.java,,"java.lang.RuntimeException: jdk.internal.org.xml.sax.SAXParseException; Internal DTD subset is not allowed. The Properties XML document must have the following DOCTYPE declaration: <!DOCTYPE properties SYSTEM ""http://java.sun.com/dtd/properties.dtd"">" -java/util/ResourceBundle/Control/XMLResourceBundleTest.java,,java.util.MissingResourceException: Can't find bundle for base name XmlRB locale -java/util/ResourceBundle/modules/xmlformat/xmlformat.sh,,Timeout reached: 60000. Process is not alive! -java/util/TimeZone/TimeZoneTest.java,,Uncaught exception thrown in test method TestShortZoneIDs -java/util/concurrent/locks/Lock/TimedAcquireLeak.java,, -java/util/jar/JarFile/mrjar/MultiReleaseJarAPI.java,,Fails in Docker -java/util/logging/LogManager/Configuration/updateConfiguration/SimpleUpdateConfigWithInputStreamTest.java,, -java/util/logging/TestLoggerWeakRefLeak.java,, -java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java,,java.util.MissingResourceException: Can't find bundle for base name com.foo.XmlRB locale -javax/imageio/AppletResourceTest.java,, -javax/imageio/plugins/jpeg/JPEGsNotAcceleratedTest.java,,java.awt.HeadlessException: No X11 DISPLAY variable was set but this program performed an operation which requires it. -javax/management/security/HashedPasswordFileTest.java,, -javax/net/ssl/DTLS/DTLSBufferOverflowUnderflowTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSDataExchangeTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSEnginesClosureTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSHandshakeTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSHandshakeWithReplicatedPacketsTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSIncorrectAppDataTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSMFLNTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSNotEnabledRC4Test.java,,Compilation failed -javax/net/ssl/DTLS/DTLSRehandshakeTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSRehandshakeWithCipherChangeTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSRehandshakeWithDataExTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSSequenceNumberTest.java,,Compilation failed -javax/net/ssl/DTLS/DTLSUnsupportedCiphersTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10BufferOverflowUnderflowTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10DataExchangeTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10EnginesClosureTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10HandshakeTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10HandshakeWithReplicatedPacketsTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10IncorrectAppDataTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10MFLNTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10NotEnabledRC4Test.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10RehandshakeTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithCipherChangeTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithDataExTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10SequenceNumberTest.java,,Compilation failed -javax/net/ssl/DTLSv10/DTLSv10UnsupportedCiphersTest.java,,Compilation failed -javax/net/ssl/SSLSession/JSSERenegotiate.java,,Fails in Docker -javax/net/ssl/TLS/TLSDataExchangeTest.java,,Compilation failed -javax/net/ssl/TLS/TLSEnginesClosureTest.java,,Compilation failed -javax/net/ssl/TLS/TLSHandshakeTest.java,,Compilation failed -javax/net/ssl/TLS/TLSMFLNTest.java,,Compilation failed -javax/net/ssl/TLS/TLSNotEnabledRC4Test.java,,Compilation failed -javax/net/ssl/TLS/TLSRehandshakeTest.java,,Compilation failed -javax/net/ssl/TLS/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed -javax/net/ssl/TLS/TLSRehandshakeWithDataExTest.java,,Compilation failed -javax/net/ssl/TLS/TLSUnsupportedCiphersTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSDataExchangeTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSEnginesClosureTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSHandshakeTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSMFLNTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSNotEnabledRC4Test.java,,Compilation failed -javax/net/ssl/TLSv1/TLSRehandshakeTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSRehandshakeWithDataExTest.java,,Compilation failed -javax/net/ssl/TLSv1/TLSUnsupportedCiphersTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSDataExchangeTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSEnginesClosureTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSHandshakeTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSMFLNTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSNotEnabledRC4Test.java,,Compilation failed -javax/net/ssl/TLSv11/TLSRehandshakeTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSRehandshakeWithDataExTest.java,,Compilation failed -javax/net/ssl/TLSv11/TLSUnsupportedCiphersTest.java,,Compilation failed -javax/net/ssl/TLSv12/TLSEnginesClosureTest.java,,Compilation failed -javax/sound/sampled/AudioInputStream/FrameLengthAfterConversion.java,, -jdk/jfr/cmd/TestHelp.java,,java.lang.RuntimeException: 'Available commands are:' missing from stdout/stderr -jdk/jfr/cmd/TestPrint.java,,Missing file' missing from stdout/stderr -jdk/jfr/cmd/TestPrintDefault.java,,java.lang.RuntimeException: 'JVMInformation' missing from stdout/stderr -jdk/jfr/cmd/TestPrintJSON.java,,javax.script.ScriptException: <eval>:1:17 Expected an operand but found eof var jsonObject = ^ in <eval> at line number 1 at column number 17 -jdk/jfr/cmd/TestPrintXML.java,,org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Premature end of file. -jdk/jfr/cmd/TestReconstruct.java,,java.lang.RuntimeException: 'Too few arguments' missing from stdout/stderr -jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr -jdk/jfr/cmd/TestSummary.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr -jdk/jfr/event/compiler/TestCompilerStats.java,,java.lang.RuntimeException: Field nmetodsSize not in event -jdk/jfr/event/metadata/TestDefaultConfigurations.java,,Setting 'threshold' in event 'jdk.SecurityPropertyModification' was not configured in the configuration 'default' -jdk/jfr/event/oldobject/TestLargeRootSet.java,,Flaky - `main' threw exception: java.lang.RuntimeException: Could not find root object -jdk/jfr/event/runtime/TestActiveSettingEvent.java,,java.lang.Exception: Could not find setting with name jdk.X509Validation#threshold -jdk/jfr/event/runtime/TestModuleEvents.java,,java.lang.RuntimeException: assertEquals: expected jdk.proxy1 to equal java.base -jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,, -jdk/jfr/event/runtime/TestThreadParkEvent.java,, -jdk/jfr/event/sampling/TestNative.java,, -jdk/jfr/javaagent/TestLoadedAgent.java,b/180542638, -jdk/jfr/jcmd/TestJcmdChangeLogLevel.java,, -jdk/jfr/jcmd/TestJcmdConfigure.java,, -jdk/jfr/jcmd/TestJcmdDump.java,, -jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java,, -jdk/jfr/jcmd/TestJcmdDumpLimited.java,, -jdk/jfr/jcmd/TestJcmdDumpPathToGCRoots.java,, -jdk/jfr/jcmd/TestJcmdDumpWithFileName.java,b/180542783, -jdk/jfr/jcmd/TestJcmdLegacy.java,, -jdk/jfr/jcmd/TestJcmdSaveToFile.java,, -jdk/jfr/jcmd/TestJcmdStartDirNotExist.java,, -jdk/jfr/jcmd/TestJcmdStartInvaldFile.java,, -jdk/jfr/jcmd/TestJcmdStartPathToGCRoots.java,, -jdk/jfr/jcmd/TestJcmdStartStopDefault.java,, -jdk/jfr/jcmd/TestJcmdStartWithOptions.java,, -jdk/jfr/jcmd/TestJcmdStartWithSettings.java,, -jdk/jfr/jcmd/TestJcmdStopInvalidFile.java,, -jdk/jfr/jmx/TestGetRecordings.java,b/180542639, -jdk/jfr/jvm/TestGetAllEventClasses.java,,Compilation failed -jdk/jfr/jvm/TestJfrJavaBase.java,, -jdk/jfr/startupargs/TestStartRecording.java,, -jdk/modules/incubator/ImageModules.java,, -jdk/net/Sockets/ExtOptionTest.java,, -jdk/net/Sockets/QuickAckTest.java,, -lib/security/cacerts/VerifyCACerts.java,, -sun/management/jmxremote/bootstrap/CustomLauncherTest.java,, -sun/management/jmxremote/bootstrap/JvmstatCountersTest.java,, -sun/management/jmxremote/bootstrap/LocalManagementTest.java,, -sun/management/jmxremote/bootstrap/RmiRegistrySslTest.java,, -sun/management/jmxremote/bootstrap/RmiSslBootstrapTest.sh,, -sun/management/jmxremote/startstop/JMXStartStopTest.java,, -sun/management/jmxremote/startstop/JMXStatusPerfCountersTest.java,, -sun/management/jmxremote/startstop/JMXStatusTest.java,, -sun/management/jdp/JdpDefaultsTest.java,, -sun/management/jdp/JdpJmxRemoteDynamicPortTest.java,, -sun/management/jdp/JdpOffTest.java,, -sun/management/jdp/JdpSpecificAddressTest.java,, -sun/text/resources/LocaleDataTest.java,, -sun/tools/jcmd/TestJcmdSanity.java,, -sun/tools/jhsdb/AlternateHashingTest.java,, -sun/tools/jhsdb/BasicLauncherTest.java,, -sun/tools/jhsdb/HeapDumpTest.java,, -sun/tools/jhsdb/heapconfig/JMapHeapConfigTest.java,, -sun/tools/jhsdb/JShellHeapDumpTest.java,,Fails on runc too -sun/tools/jinfo/BasicJInfoTest.java,, -sun/tools/jinfo/JInfoTest.java,, -sun/tools/jmap/BasicJMapTest.java,, -sun/tools/jstack/BasicJStackTest.java,, -sun/tools/jstack/DeadlockDetectionTest.java,, -sun/tools/jstatd/TestJstatdExternalRegistry.java,, -sun/tools/jstatd/TestJstatdPort.java,,Flaky -sun/tools/jstatd/TestJstatdPortAndServer.java,,Flaky -sun/util/calendar/zi/TestZoneInfo310.java,, -tools/jar/modularJar/Basic.java,, -tools/jar/multiRelease/Basic.java,, -tools/jimage/JImageExtractTest.java,, -tools/jimage/JImageTest.java,, -tools/jlink/JLinkTest.java,, -tools/jlink/plugins/IncludeLocalesPluginTest.java,, -tools/jmod/hashes/HashesTest.java,, -tools/launcher/BigJar.java,b/111611473, -tools/launcher/HelpFlagsTest.java,,java.lang.AssertionError: HelpFlagsTest failed: Tool jfr not covered by this test. Add specification to jdkTools array! -tools/launcher/JliLaunchTest.java,,Fails on runc too -tools/launcher/VersionCheck.java,,java.lang.AssertionError: VersionCheck failed: testToolVersion: [jfr]; -tools/launcher/modules/patch/systemmodules/PatchSystemModules.java,, diff --git a/test/runtimes/exclude/nodejs12.4.0.csv b/test/runtimes/exclude/nodejs12.4.0.csv deleted file mode 100644 index c4e7917ec..000000000 --- a/test/runtimes/exclude/nodejs12.4.0.csv +++ /dev/null @@ -1,45 +0,0 @@ -test name,bug id,comment -async-hooks/test-statwatcher.js,https://github.com/nodejs/node/issues/21425,Check for fix inclusion in nodejs releases after 2020-03-29 -benchmark/test-benchmark-fs.js,,Broken test -benchmark/test-benchmark-napi.js,,Broken test -doctool/test-make-doc.js,b/68848110,Expected to fail. -internet/test-dgram-multicast-set-interface-lo.js,b/162798882, -internet/test-doctool-versions.js,,Broken test -internet/test-uv-threadpool-schedule.js,,Broken test -parallel/test-dgram-bind-fd.js,b/132447356, -parallel/test-dgram-socket-buffer-size.js,b/68847921, -parallel/test-dns-channel-timeout.js,b/161893056, -parallel/test-fs-access.js,,Broken test -parallel/test-fs-watchfile.js,b/166819807,Flaky - VFS1 only -parallel/test-fs-write-stream.js,b/166819807,Flaky - VFS1 only -parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky - VFS1 only -parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky - VFS1 only -parallel/test-http-writable-true-after-close.js,b/171301436,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2 -parallel/test-os.js,b/63997097, -parallel/test-process-uid-gid.js,,Does not work inside Docker with gid nobody -pseudo-tty/test-assert-colors.js,b/162801321, -pseudo-tty/test-assert-no-color.js,b/162801321, -pseudo-tty/test-assert-position-indicator.js,b/162801321, -pseudo-tty/test-async-wrap-getasyncid-tty.js,b/162801321, -pseudo-tty/test-fatal-error.js,b/162801321, -pseudo-tty/test-handle-wrap-isrefed-tty.js,b/162801321, -pseudo-tty/test-readable-tty-keepalive.js,b/162801321, -pseudo-tty/test-set-raw-mode-reset-process-exit.js,b/162801321, -pseudo-tty/test-set-raw-mode-reset-signal.js,b/162801321, -pseudo-tty/test-set-raw-mode-reset.js,b/162801321, -pseudo-tty/test-stderr-stdout-handle-sigwinch.js,b/162801321, -pseudo-tty/test-stdout-read.js,b/162801321, -pseudo-tty/test-tty-color-support.js,b/162801321, -pseudo-tty/test-tty-isatty.js,b/162801321, -pseudo-tty/test-tty-stdin-call-end.js,b/162801321, -pseudo-tty/test-tty-stdin-end.js,b/162801321, -pseudo-tty/test-stdin-write.js,b/162801321, -pseudo-tty/test-tty-stdout-end.js,b/162801321, -pseudo-tty/test-tty-stdout-resize.js,b/162801321, -pseudo-tty/test-tty-stream-constructors.js,b/162801321, -pseudo-tty/test-tty-window-size.js,b/162801321, -pseudo-tty/test-tty-wrap.js,b/162801321, -pummel/test-net-pingpong.js,,Broken test -pummel/test-vm-memleak.js,b/162799436, -pummel/test-watch-file.js,,Flaky - VFS1 only -tick-processor/test-tick-processor-builtin.js,,Broken test diff --git a/test/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv deleted file mode 100644 index c051fe571..000000000 --- a/test/runtimes/exclude/php7.3.6.csv +++ /dev/null @@ -1,48 +0,0 @@ -test name,bug id,comment -ext/intl/tests/bug77895.phpt,, -ext/intl/tests/dateformat_bug65683_2.phpt,, -ext/mbstring/tests/bug76319.phpt,, -ext/mbstring/tests/bug76958.phpt,, -ext/mbstring/tests/bug77025.phpt,, -ext/mbstring/tests/bug77165.phpt,, -ext/mbstring/tests/bug77454.phpt,, -ext/mbstring/tests/mb_convert_encoding_leak.phpt,, -ext/mbstring/tests/mb_strrpos_encoding_3rd_param.phpt,, -ext/pcre/tests/cache_limit.phpt,,Broken test - Flaky -ext/session/tests/session_module_name_variation4.phpt,,Flaky -ext/session/tests/session_set_save_handler_class_018.phpt,, -ext/session/tests/session_set_save_handler_iface_003.phpt,, -ext/session/tests/session_set_save_handler_sid_001.phpt,, -ext/session/tests/session_set_save_handler_variation4.phpt,, -ext/standard/tests/file/disk.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_free_space_basic.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_free_space_error.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_free_space_variation.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_total_space_basic.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_total_space_error.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/disk_total_space_variation.phpt,https://bugs.php.net/bug.php?id=80018, -ext/standard/tests/file/fopen_variation19.phpt,b/162894964, -ext/standard/tests/file/lstat_stat_variation14.phpt,,Flaky -ext/standard/tests/file/php_fd_wrapper_01.phpt,, -ext/standard/tests/file/php_fd_wrapper_02.phpt,, -ext/standard/tests/file/php_fd_wrapper_03.phpt,, -ext/standard/tests/file/php_fd_wrapper_04.phpt,, -ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,VFS1 only failure -ext/standard/tests/file/rename_variation.phpt,b/68717309, -ext/standard/tests/file/symlink_link_linkinfo_is_link_variation4.phpt,b/162895341, -ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,VFS1 only failure -ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,, -ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,, -ext/standard/tests/streams/proc_open_bug60120.phpt,,Flaky until php-src 3852a35fdbcb -ext/standard/tests/streams/proc_open_bug64438.phpt,,Flaky -ext/standard/tests/streams/proc_open_bug69900.phpt,,Flaky -ext/standard/tests/streams/stream_socket_sendto.phpt,, -ext/standard/tests/strings/007.phpt,, -sapi/cli/tests/upload_2G.phpt,, -tests/output/stream_isatty_err.phpt,b/68720279, -tests/output/stream_isatty_in-err.phpt,b/68720282, -tests/output/stream_isatty_in-out-err.phpt,, -tests/output/stream_isatty_in-out.phpt,b/68720299, -tests/output/stream_isatty_out-err.phpt,b/68720311, -tests/output/stream_isatty_out.phpt,b/68720325, -Zend/tests/concat_003.phpt,b/162896021, diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv deleted file mode 100644 index e9fef03b7..000000000 --- a/test/runtimes/exclude/python3.7.3.csv +++ /dev/null @@ -1,19 +0,0 @@ -test name,bug id,comment -test_asyncio,,Fails on Docker. -test_asyncore,b/162973328, -test_epoll,b/162983393, -test_fcntl,b/162978767,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode. -test_httplib,b/163000009,OSError: [Errno 98] Address already in use -test_logging,b/162980079, -test_multiprocessing_fork,,Flaky. Sometimes times out. -test_multiprocessing_forkserver,,Flaky. Sometimes times out. -test_multiprocessing_main_handling,,Flaky. Sometimes times out. -test_multiprocessing_spawn,,Flaky. Sometimes times out. -test_posix,b/76174079,posix.sched_get_priority_min not implemented + posix.sched_rr_get_interval not permitted -test_pty,b/162979921, -test_readline,b/162980389,TestReadline hangs forever -test_resource,b/76174079, -test_selectors,b/76116849,OSError not raised with epoll -test_smtplib,b/162980434,unclosed sockets -test_signal,,Flaky - signal: alarm clock -test_socket,b/75983380, diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD deleted file mode 100644 index b4a9b12de..000000000 --- a/test/runtimes/proctor/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "proctor", - srcs = ["main.go"], - pure = True, - visibility = ["//test/runtimes:__pkg__"], - deps = [ - "//test/runtimes/proctor/lib", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/test/runtimes/proctor/lib/BUILD b/test/runtimes/proctor/lib/BUILD deleted file mode 100644 index f834f1b5a..000000000 --- a/test/runtimes/proctor/lib/BUILD +++ /dev/null @@ -1,25 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "lib", - srcs = [ - "go.go", - "java.go", - "lib.go", - "nodejs.go", - "php.go", - "python.go", - ], - visibility = ["//test/runtimes/proctor:__pkg__"], - deps = ["@org_golang_x_sys//unix:go_default_library"], -) - -go_test( - name = "lib_test", - size = "small", - srcs = ["lib_test.go"], - library = ":lib", - deps = ["//pkg/test/testutil"], -) diff --git a/test/runtimes/proctor/lib/go.go b/test/runtimes/proctor/lib/go.go deleted file mode 100644 index 5c48fb60b..000000000 --- a/test/runtimes/proctor/lib/go.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "fmt" - "os" - "os/exec" - "regexp" - "strings" -) - -var ( - goTestRegEx = regexp.MustCompile(`^.+\.go$`) - - // Directories with .dir contain helper files for tests. - // Exclude benchmarks and stress tests. - goDirFilter = regexp.MustCompile(`^(bench|stress)\/.+$|^.+\.dir.+$`) -) - -// Location of Go tests on disk. -const goTestDir = "/usr/local/go/test" - -// goRunner implements TestRunner for Go. -// -// There are two types of Go tests: "Go tool tests" and "Go tests on disk". -// "Go tool tests" are found and executed using `go tool dist test`. "Go tests -// on disk" are found in the /usr/local/go/test directory and are executed -// using `go run run.go`. -type goRunner struct{} - -var _ TestRunner = goRunner{} - -// ListTests implements TestRunner.ListTests. -func (goRunner) ListTests() ([]string, error) { - // Go tool dist test tests. - args := []string{"tool", "dist", "test", "-list"} - cmd := exec.Command("go", args...) - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to list: %v", err) - } - var toolSlice []string - for _, test := range strings.Split(string(out), "\n") { - toolSlice = append(toolSlice, test) - } - - // Go tests on disk. - diskSlice, err := Search(goTestDir, goTestRegEx) - if err != nil { - return nil, err - } - // Remove items from /bench/, /stress/ and .dir files - diskFiltered := diskSlice[:0] - for _, file := range diskSlice { - if !goDirFilter.MatchString(file) { - diskFiltered = append(diskFiltered, file) - } - } - - return append(toolSlice, diskFiltered...), nil -} - -// TestCmds implements TestRunner.TestCmds. -func (goRunner) TestCmds(tests []string) []*exec.Cmd { - var toolTests, onDiskTests []string - for _, test := range tests { - if strings.HasSuffix(test, ".go") { - onDiskTests = append(onDiskTests, test) - } else { - toolTests = append(toolTests, "^"+test+"$") - } - } - - var cmds []*exec.Cmd - if len(toolTests) > 0 { - cmds = append(cmds, exec.Command("go", "tool", "dist", "test", "-v", "-no-rebuild", "-run", strings.Join(toolTests, "\\|"))) - } - if len(onDiskTests) > 0 { - cmd := exec.Command("go", append([]string{"run", "run.go", "-v", "--"}, onDiskTests...)...) - cmd.Dir = goTestDir - cmds = append(cmds, cmd) - } - - return cmds -} diff --git a/test/runtimes/proctor/lib/java.go b/test/runtimes/proctor/lib/java.go deleted file mode 100644 index 3105011ff..000000000 --- a/test/runtimes/proctor/lib/java.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "fmt" - "os" - "os/exec" - "regexp" - "strings" -) - -// Directories to exclude from tests. -var javaExclDirs = regexp.MustCompile(`(^(sun\/security)|(java\/util\/stream)|(java\/time)| )`) - -// Location of java tests. -const javaTestDir = "/root/test/jdk" - -// javaRunner implements TestRunner for Java. -type javaRunner struct{} - -var _ TestRunner = javaRunner{} - -// ListTests implements TestRunner.ListTests. -func (javaRunner) ListTests() ([]string, error) { - args := []string{ - "-dir:" + javaTestDir, - "-ignore:quiet", - "-a", - "-listtests", - ":jdk_core", - ":jdk_svc", - ":jdk_sound", - ":jdk_imageio", - } - cmd := exec.Command("jtreg", args...) - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("jtreg -listtests : %v", err) - } - var testSlice []string - for _, test := range strings.Split(string(out), "\n") { - if !javaExclDirs.MatchString(test) { - testSlice = append(testSlice, test) - } - } - return testSlice, nil -} - -// TestCmds implements TestRunner.TestCmds. -func (javaRunner) TestCmds(tests []string) []*exec.Cmd { - args := append( - []string{ - "-agentvm", // Execute each action using a pool of reusable JVMs. - "-dir:" + javaTestDir, // Base directory for test files and directories. - "-noreport", // Do not generate a final report. - "-timeoutFactor:20", // Extend the default timeout (2 min) of all tests by this factor. - "-verbose:nopass", // Verbose output but supress it for tests that passed. - }, - tests..., - ) - return []*exec.Cmd{exec.Command("jtreg", args...)} -} diff --git a/test/runtimes/proctor/lib/lib.go b/test/runtimes/proctor/lib/lib.go deleted file mode 100644 index 36c60088a..000000000 --- a/test/runtimes/proctor/lib/lib.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package lib contains proctor functions. -package lib - -import ( - "fmt" - "os" - "os/exec" - "os/signal" - "path/filepath" - "regexp" - - "golang.org/x/sys/unix" -) - -// TestRunner is an interface that must be implemented for each runtime -// integrated with proctor. -type TestRunner interface { - // ListTests returns a string slice of tests available to run. - ListTests() ([]string, error) - - // TestCmds returns a slice of *exec.Cmd that will run the given tests. - // There is no correlation between the number of exec.Cmds returned and the - // number of tests. It could return one command to run all tests or a few - // commands that collectively run all. - TestCmds(tests []string) []*exec.Cmd -} - -// TestRunnerForRuntime returns a new TestRunner for the given runtime. -func TestRunnerForRuntime(runtime string) (TestRunner, error) { - switch runtime { - case "go": - return goRunner{}, nil - case "java": - return javaRunner{}, nil - case "nodejs": - return nodejsRunner{}, nil - case "php": - return phpRunner{}, nil - case "python": - return pythonRunner{}, nil - } - return nil, fmt.Errorf("invalid runtime %q", runtime) -} - -// PauseAndReap is like init. It runs forever and reaps any children. -func PauseAndReap() { - // Get notified of any new children. - ch := make(chan os.Signal, 1) - signal.Notify(ch, unix.SIGCHLD) - - for { - if _, ok := <-ch; !ok { - // Channel closed. This should not happen. - panic("signal channel closed") - } - - // Reap the child. - for { - if cpid, _ := unix.Wait4(-1, nil, unix.WNOHANG, nil); cpid < 1 { - break - } - } - } -} - -// Search is a helper function to find tests in the given directory that match -// the regex. -func Search(root string, testFilter *regexp.Regexp) ([]string, error) { - var testSlice []string - - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - name := filepath.Base(path) - - if info.IsDir() || !testFilter.MatchString(name) { - return nil - } - - relPath, err := filepath.Rel(root, path) - if err != nil { - return err - } - testSlice = append(testSlice, relPath) - return nil - }) - if err != nil { - return nil, fmt.Errorf("walking %q: %v", root, err) - } - - return testSlice, nil -} diff --git a/test/runtimes/proctor/lib/lib_test.go b/test/runtimes/proctor/lib/lib_test.go deleted file mode 100644 index 1193d2e28..000000000 --- a/test/runtimes/proctor/lib/lib_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "io/ioutil" - "os" - "path/filepath" - "reflect" - "regexp" - "strings" - "testing" - - "gvisor.dev/gvisor/pkg/test/testutil" -) - -func touch(t *testing.T, name string) { - t.Helper() - f, err := os.Create(name) - if err != nil { - t.Fatalf("error creating file %q: %v", name, err) - } - if err := f.Close(); err != nil { - t.Fatalf("error closing file %q: %v", name, err) - } -} - -func TestSearchEmptyDir(t *testing.T) { - td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest") - if err != nil { - t.Fatalf("error creating searchtest: %v", err) - } - defer os.RemoveAll(td) - - var want []string - - testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`) - got, err := Search(td, testFilter) - if err != nil { - t.Errorf("search error: %v", err) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Found %#v; want %#v", got, want) - } -} - -func TestSearch(t *testing.T) { - td, err := ioutil.TempDir(testutil.TmpDir(), "searchtest") - if err != nil { - t.Fatalf("error creating searchtest: %v", err) - } - defer os.RemoveAll(td) - - // Creating various files similar to the test filter regex. - files := []string{ - "emp/", - "tee/", - "test-foo.tc", - "test-foo.tc", - "test-bar.tc", - "test-sam.tc", - "Test-que.tc", - "test-brett", - "test--abc.tc", - "test---xyz.tc", - "test-bool.TC", - "--test-gvs.tc", - " test-pew.tc", - "dir/test_baz.tc", - "dir/testsnap.tc", - "dir/test-luk.tc", - "dir/nest/test-ok.tc", - "dir/dip/diz/goog/test-pack.tc", - "dir/dip/diz/wobble/thud/test-cas.e", - "dir/dip/diz/wobble/thud/test-cas.tc", - } - want := []string{ - "dir/dip/diz/goog/test-pack.tc", - "dir/dip/diz/wobble/thud/test-cas.tc", - "dir/nest/test-ok.tc", - "dir/test-luk.tc", - "test-bar.tc", - "test-foo.tc", - "test-sam.tc", - } - - for _, item := range files { - if strings.HasSuffix(item, "/") { - // This item is a directory, create it. - if err := os.MkdirAll(filepath.Join(td, item), 0755); err != nil { - t.Fatalf("error making directory: %v", err) - } - } else { - // This item is a file, create the directory and touch file. - // Create directory in which file should be created - fullDirPath := filepath.Join(td, filepath.Dir(item)) - if err := os.MkdirAll(fullDirPath, 0755); err != nil { - t.Fatalf("error making directory: %v", err) - } - // Create file with full path to file. - touch(t, filepath.Join(td, item)) - } - } - - testFilter := regexp.MustCompile(`^test-[^-].+\.tc$`) - got, err := Search(td, testFilter) - if err != nil { - t.Errorf("search error: %v", err) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Found %#v; want %#v", got, want) - } -} diff --git a/test/runtimes/proctor/lib/nodejs.go b/test/runtimes/proctor/lib/nodejs.go deleted file mode 100644 index 320597aa5..000000000 --- a/test/runtimes/proctor/lib/nodejs.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "os/exec" - "path/filepath" - "regexp" -) - -var nodejsTestRegEx = regexp.MustCompile(`^test-[^-].+\.js$`) - -// Location of nodejs tests relative to working dir. -const nodejsTestDir = "test" - -// nodejsRunner implements TestRunner for NodeJS. -type nodejsRunner struct{} - -var _ TestRunner = nodejsRunner{} - -// ListTests implements TestRunner.ListTests. -func (nodejsRunner) ListTests() ([]string, error) { - testSlice, err := Search(nodejsTestDir, nodejsTestRegEx) - if err != nil { - return nil, err - } - return testSlice, nil -} - -// TestCmds implements TestRunner.TestCmds. -func (nodejsRunner) TestCmds(tests []string) []*exec.Cmd { - args := append([]string{filepath.Join("tools", "test.py"), "--timeout=180"}, tests...) - return []*exec.Cmd{exec.Command("/usr/bin/python", args...)} -} diff --git a/test/runtimes/proctor/lib/php.go b/test/runtimes/proctor/lib/php.go deleted file mode 100644 index b67a60a97..000000000 --- a/test/runtimes/proctor/lib/php.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "os/exec" - "regexp" - "strings" -) - -var phpTestRegEx = regexp.MustCompile(`^.+\.phpt$`) - -// phpRunner implements TestRunner for PHP. -type phpRunner struct{} - -var _ TestRunner = phpRunner{} - -// ListTests implements TestRunner.ListTests. -func (phpRunner) ListTests() ([]string, error) { - testSlice, err := Search(".", phpTestRegEx) - if err != nil { - return nil, err - } - return testSlice, nil -} - -// TestCmds implements TestRunner.TestCmds. -func (phpRunner) TestCmds(tests []string) []*exec.Cmd { - args := []string{"test", "TESTS=" + strings.Join(tests, " ")} - return []*exec.Cmd{exec.Command("make", args...)} -} diff --git a/test/runtimes/proctor/lib/python.go b/test/runtimes/proctor/lib/python.go deleted file mode 100644 index 429bfd850..000000000 --- a/test/runtimes/proctor/lib/python.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "fmt" - "os" - "os/exec" - "strings" -) - -// pythonRunner implements TestRunner for Python. -type pythonRunner struct{} - -var _ TestRunner = pythonRunner{} - -// ListTests implements TestRunner.ListTests. -func (pythonRunner) ListTests() ([]string, error) { - args := []string{"-m", "test", "--list-tests"} - cmd := exec.Command("./python", args...) - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to list: %v", err) - } - var toolSlice []string - for _, test := range strings.Split(string(out), "\n") { - toolSlice = append(toolSlice, test) - } - return toolSlice, nil -} - -// TestCmds implements TestRunner.TestCmds. -func (pythonRunner) TestCmds(tests []string) []*exec.Cmd { - args := append([]string{"-m", "test"}, tests...) - return []*exec.Cmd{exec.Command("./python", args...)} -} diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go deleted file mode 100644 index 8c076a499..000000000 --- a/test/runtimes/proctor/main.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Binary proctor runs the test for a particular runtime. It is meant to be -// included in Docker images for all runtime tests. -package main - -import ( - "flag" - "fmt" - "log" - "os" - "strings" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/test/runtimes/proctor/lib" -) - -var ( - runtime = flag.String("runtime", "", "name of runtime") - list = flag.Bool("list", false, "list all available tests") - testNames = flag.String("tests", "", "run a subset of the available tests") - pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children") -) - -// setNumFilesLimit changes the NOFILE soft rlimit if it is too high. -func setNumFilesLimit() error { - // In docker containers, the default value of the NOFILE limit is - // 1048576. A few runtime tests (e.g. python:test_subprocess) - // enumerates all possible file descriptors and these tests can fail by - // timeout if the NOFILE limit is too high. On gVisor, syscalls are - // slower so these tests will need even more time to pass. - const nofile = 32768 - rLimit := unix.Rlimit{} - err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit) - if err != nil { - return fmt.Errorf("failed to get RLIMIT_NOFILE: %v", err) - } - if rLimit.Cur > nofile { - rLimit.Cur = nofile - err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit) - if err != nil { - return fmt.Errorf("failed to set RLIMIT_NOFILE: %v", err) - } - } - return nil -} - -func main() { - flag.Parse() - - if *pause { - lib.PauseAndReap() - panic("pauseAndReap should never return") - } - - if *runtime == "" { - log.Fatalf("runtime flag must be provided") - } - - tr, err := lib.TestRunnerForRuntime(*runtime) - if err != nil { - log.Fatalf("%v", err) - } - - // List tests. - if *list { - tests, err := tr.ListTests() - if err != nil { - log.Fatalf("failed to list tests: %v", err) - } - for _, test := range tests { - fmt.Println(test) - } - return - } - - var tests []string - if *testNames == "" { - // Run every test. - tests, err = tr.ListTests() - if err != nil { - log.Fatalf("failed to get all tests: %v", err) - } - } else { - // Run subset of test. - tests = strings.Split(*testNames, ",") - } - - if err := setNumFilesLimit(); err != nil { - log.Fatalf("%v", err) - } - - // Run tests. - cmds := tr.TestCmds(tests) - for _, cmd := range cmds { - cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr - if err := cmd.Run(); err != nil { - log.Fatalf("FAIL: %v", err) - } - } -} diff --git a/test/runtimes/runner/BUILD b/test/runtimes/runner/BUILD deleted file mode 100644 index 70cc01594..000000000 --- a/test/runtimes/runner/BUILD +++ /dev/null @@ -1,11 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "runner", - testonly = 1, - srcs = ["main.go"], - visibility = ["//test/runtimes:__pkg__"], - deps = ["//test/runtimes/runner/lib"], -) diff --git a/test/runtimes/runner/lib/BUILD b/test/runtimes/runner/lib/BUILD deleted file mode 100644 index d308f41b0..000000000 --- a/test/runtimes/runner/lib/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "lib", - testonly = 1, - srcs = ["lib.go"], - visibility = ["//test/runtimes/runner:__pkg__"], - deps = [ - "//pkg/log", - "//pkg/test/dockerutil", - "//pkg/test/testutil", - ], -) - -go_test( - name = "lib_test", - size = "small", - srcs = ["exclude_test.go"], - library = ":lib", -) diff --git a/test/runtimes/runner/lib/exclude_test.go b/test/runtimes/runner/lib/exclude_test.go deleted file mode 100644 index f996e895b..000000000 --- a/test/runtimes/runner/lib/exclude_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package lib - -import ( - "flag" - "os" - "testing" -) - -var excludeFile = flag.String("exclude_file", "", "file to test (standard format)") - -func TestMain(m *testing.M) { - flag.Parse() - os.Exit(m.Run()) -} - -// Test that the exclude file parses without error. -func TestExcludelist(t *testing.T) { - ex, err := getExcludes(*excludeFile) - if err != nil { - t.Fatalf("error parsing exclude file: %v", err) - } - if *excludeFile != "" && len(ex) == 0 { - t.Errorf("got empty excludes for file %q", *excludeFile) - } -} diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go deleted file mode 100644 index f2db5f9ea..000000000 --- a/test/runtimes/runner/lib/lib.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package lib provides utilities for runner. -package lib - -import ( - "context" - "encoding/csv" - "fmt" - "io" - "os" - "sort" - "strings" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// RunTests is a helper that is called by main. It exists so that we can run -// defered functions before exiting. It returns an exit code that should be -// passed to os.Exit. -func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int { - // TODO(gvisor.dev/issue/1624): Remove those tests from all exclude lists - // that only fail with VFS1. - - // Get tests to exclude. - excludes, err := getExcludes(excludeFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error()) - return 1 - } - - // Construct the shared docker instance. - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, testutil.DefaultLogger(lang)) - defer d.CleanUp(ctx) - - if err := testutil.TouchShardStatusFile(); err != nil { - fmt.Fprintf(os.Stderr, "error touching status shard file: %v\n", err) - return 1 - } - - // Get a slice of tests to run. This will also start a single Docker - // container that will be used to run each test. The final test will - // stop the Docker container. - tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - return 1 - } - - m := testing.MainStart(testDeps{}, tests, nil, nil) - return m.Run() -} - -// getTests executes all tests as table tests. -func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) { - // Start the container. - opts := dockerutil.RunOpts{ - Image: fmt.Sprintf("runtimes/%s", image), - } - d.CopyFiles(&opts, "/proctor", "test/runtimes/proctor/proctor") - if err := d.Spawn(ctx, opts, "/proctor/proctor", "--pause"); err != nil { - return nil, fmt.Errorf("docker run failed: %v", err) - } - - // Get a list of all tests in the image. - list, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--list") - if err != nil { - return nil, fmt.Errorf("docker exec failed: %v", err) - } - - // Calculate a subset of tests. - tests := strings.Fields(list) - sort.Strings(tests) - indices, err := testutil.TestIndicesForShard(len(tests)) - if err != nil { - return nil, fmt.Errorf("TestsForShard() failed: %v", err) - } - - var itests []testing.InternalTest - for i := 0; i < len(indices); i += batchSize { - var tcs []string - end := i + batchSize - if end > len(indices) { - end = len(indices) - } - for _, tc := range indices[i:end] { - // Add test if not excluded. - if _, ok := excludes[tests[tc]]; ok { - log.Infof("Skipping test case %s\n", tests[tc]) - continue - } - tcs = append(tcs, tests[tc]) - } - if len(tcs) == 0 { - // No tests to add to this batch. - continue - } - itests = append(itests, testing.InternalTest{ - Name: strings.Join(tcs, ", "), - F: func(t *testing.T) { - var ( - now = time.Now() - done = make(chan struct{}) - output string - err error - ) - - state, err := d.Status(ctx) - if err != nil { - t.Fatalf("Could not find container status: %v", err) - } - if !state.Running { - t.Fatalf("container is not running: state = %s", state.Status) - } - - go func() { - output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--tests", strings.Join(tcs, ",")) - close(done) - }() - - select { - case <-done: - if err == nil { - fmt.Printf("PASS: (%v) %d tests passed\n", time.Since(now), len(tcs)) - return - } - t.Errorf("FAIL: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output) - case <-time.After(timeout): - t.Errorf("TIMEOUT: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output) - } - }, - }) - } - - return itests, nil -} - -// getBlacklist reads the exclude file and returns a set of test names to -// exclude. -func getExcludes(excludeFile string) (map[string]struct{}, error) { - excludes := make(map[string]struct{}) - if excludeFile == "" { - return excludes, nil - } - f, err := os.Open(excludeFile) - if err != nil { - return nil, err - } - defer f.Close() - - r := csv.NewReader(f) - - // First line is header. Skip it. - if _, err := r.Read(); err != nil { - return nil, err - } - - for { - record, err := r.Read() - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - excludes[record[0]] = struct{}{} - } - return excludes, nil -} - -// testDeps implements testing.testDeps (an unexported interface), and is -// required to use testing.MainStart. -type testDeps struct{} - -func (f testDeps) MatchString(a, b string) (bool, error) { return a == b, nil } -func (f testDeps) StartCPUProfile(io.Writer) error { return nil } -func (f testDeps) StopCPUProfile() {} -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/runtimes/runner/main.go b/test/runtimes/runner/main.go deleted file mode 100644 index ec79a22c2..000000000 --- a/test/runtimes/runner/main.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Binary runner runs the runtime tests in a Docker container. -package main - -import ( - "flag" - "fmt" - "os" - "time" - - "gvisor.dev/gvisor/test/runtimes/runner/lib" -) - -var ( - lang = flag.String("lang", "", "language runtime to test") - image = flag.String("image", "", "docker image with runtime tests") - excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment") - batchSize = flag.Int("batch", 50, "number of test cases run in one command") - timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout") -) - -func main() { - flag.Parse() - if *lang == "" || *image == "" { - fmt.Fprintf(os.Stderr, "lang and image flags must not be empty\n") - os.Exit(1) - } - os.Exit(lib.RunTests(*lang, *image, *excludeFile, *batchSize, *timeout)) -} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD deleted file mode 100644 index ef299799e..000000000 --- a/test/syscalls/BUILD +++ /dev/null @@ -1,997 +0,0 @@ -load("//tools:defs.bzl", "more_shards", "most_shards") -load("//test/runner:defs.bzl", "syscall_test") - -package(licenses = ["notice"]) - -syscall_test( - test = "//test/syscalls/linux:32bit_test", -) - -syscall_test( - test = "//test/syscalls/linux:accept_bind_stream_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:accept_bind_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:access_test", -) - -syscall_test( - test = "//test/syscalls/linux:affinity_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:aio_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:alarm_test", -) - -syscall_test( - test = "//test/syscalls/linux:arch_prctl_test", -) - -syscall_test( - test = "//test/syscalls/linux:bad_test", -) - -syscall_test( - size = "large", - add_overlay = True, - test = "//test/syscalls/linux:bind_test", -) - -syscall_test( - test = "//test/syscalls/linux:brk_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_capability_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_stress_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:chdir_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:chmod_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:chown_test", - use_tmpfs = True, # chown tests require gofer to be running as root. -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:chroot_test", -) - -syscall_test( - test = "//test/syscalls/linux:clock_getres_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:clock_gettime_test", -) - -syscall_test( - test = "//test/syscalls/linux:clock_nanosleep_test", -) - -syscall_test( - test = "//test/syscalls/linux:concurrency_test", -) - -syscall_test( - add_uds_tree = True, - test = "//test/syscalls/linux:connect_external_test", - use_tmpfs = True, -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:creat_test", -) - -syscall_test( - fuse = "True", - test = "//test/syscalls/linux:dev_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:dup_test", -) - -syscall_test( - test = "//test/syscalls/linux:epoll_test", -) - -syscall_test( - test = "//test/syscalls/linux:eventfd_test", -) - -syscall_test( - test = "//test/syscalls/linux:exceptions_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:exec_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:exec_binary_test", -) - -syscall_test( - test = "//test/syscalls/linux:exit_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:fadvise64_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:fallocate_test", -) - -syscall_test( - test = "//test/syscalls/linux:fault_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:fchdir_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:fcntl_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:flock_test", -) - -syscall_test( - test = "//test/syscalls/linux:fork_test", -) - -syscall_test( - test = "//test/syscalls/linux:fpsig_fork_test", -) - -syscall_test( - test = "//test/syscalls/linux:fpsig_nested_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:fsync_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:futex_test", -) - -syscall_test( - test = "//test/syscalls/linux:getcpu_host_test", -) - -syscall_test( - test = "//test/syscalls/linux:getcpu_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:getdents_test", -) - -syscall_test( - test = "//test/syscalls/linux:getrandom_test", -) - -syscall_test( - test = "//test/syscalls/linux:getrusage_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:inotify_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:ioctl_test", -) - -syscall_test( - test = "//test/syscalls/linux:iptables_test", -) - -syscall_test( - test = "//test/syscalls/linux:ip6tables_test", -) - -syscall_test( - size = "large", - shard_count = more_shards, - test = "//test/syscalls/linux:itimer_test", -) - -syscall_test( - test = "//test/syscalls/linux:kcov_test", -) - -syscall_test( - test = "//test/syscalls/linux:kill_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:link_test", - use_tmpfs = True, # gofer needs CAP_DAC_READ_SEARCH to use AT_EMPTY_PATH with linkat(2) -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:lseek_test", -) - -syscall_test( - test = "//test/syscalls/linux:madvise_test", -) - -syscall_test( - test = "//test/syscalls/linux:membarrier_test", -) - -syscall_test( - test = "//test/syscalls/linux:memory_accounting_test", -) - -syscall_test( - test = "//test/syscalls/linux:mempolicy_test", -) - -syscall_test( - test = "//test/syscalls/linux:mincore_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:mkdir_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:mknod_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:mmap_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:mount_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:mremap_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:msync_test", -) - -syscall_test( - test = "//test/syscalls/linux:munmap_test", -) - -syscall_test( - test = "//test/syscalls/linux:network_namespace_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:open_create_test", -) - -syscall_test( - add_overlay = True, - shard_count = more_shards, - test = "//test/syscalls/linux:open_test", -) - -syscall_test( - test = "//test/syscalls/linux:packet_socket_raw_test", -) - -syscall_test( - test = "//test/syscalls/linux:packet_socket_test", -) - -syscall_test( - test = "//test/syscalls/linux:partial_bad_buffer_test", -) - -syscall_test( - test = "//test/syscalls/linux:pause_test", -) - -syscall_test( - size = "medium", - # Takes too long under gotsan to run. - tags = ["nogotsan"], - test = "//test/syscalls/linux:ping_socket_test", -) - -syscall_test( - size = "large", - add_overlay = True, - shard_count = more_shards, - test = "//test/syscalls/linux:pipe_test", -) - -syscall_test( - test = "//test/syscalls/linux:poll_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:ppoll_test", -) - -syscall_test( - test = "//test/syscalls/linux:prctl_setuid_test", -) - -syscall_test( - test = "//test/syscalls/linux:prctl_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:pread64_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:preadv_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:preadv2_test", -) - -syscall_test( - test = "//test/syscalls/linux:priority_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:proc_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_net_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_pid_oomscore_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_pid_smaps_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_pid_uid_gid_map_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:pselect_test", -) - -syscall_test( - test = "//test/syscalls/linux:ptrace_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:pty_test", -) - -syscall_test( - test = "//test/syscalls/linux:pty_root_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:pwritev2_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:pwrite64_test", -) - -syscall_test( - test = "//test/syscalls/linux:raw_socket_hdrincl_test", -) - -syscall_test( - test = "//test/syscalls/linux:raw_socket_icmp_test", -) - -syscall_test( - shard_count = more_shards, - test = "//test/syscalls/linux:raw_socket_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:read_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:readahead_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:readv_socket_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:readv_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:rename_test", -) - -syscall_test( - test = "//test/syscalls/linux:rlimits_test", -) - -syscall_test( - test = "//test/syscalls/linux:rseq_test", -) - -syscall_test( - test = "//test/syscalls/linux:rtsignal_test", -) - -syscall_test( - test = "//test/syscalls/linux:signalfd_test", -) - -syscall_test( - test = "//test/syscalls/linux:sched_test", -) - -syscall_test( - test = "//test/syscalls/linux:sched_yield_test", -) - -syscall_test( - test = "//test/syscalls/linux:seccomp_test", -) - -syscall_test( - test = "//test/syscalls/linux:select_test", -) - -syscall_test( - shard_count = more_shards, - test = "//test/syscalls/linux:semaphore_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:sendfile_socket_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:sendfile_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:setgid_test", - # setgid tests require the gofer's user namespace to have multiple groups, - # but bazel only provides one. - use_tmpfs = True, -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:splice_test", -) - -syscall_test( - test = "//test/syscalls/linux:sigaction_test", -) - -# TODO(b/119826902): Enable once the test passes in runsc. -# syscall_test(vfs2="True",test = "//test/syscalls/linux:sigaltstack_test") - -syscall_test( - test = "//test/syscalls/linux:sigreturn_test", -) - -syscall_test( - test = "//test/syscalls/linux:sigprocmask_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:sigstop_test", -) - -syscall_test( - test = "//test/syscalls/linux:sigtimedwait_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:shm_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_abstract_non_blocking_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_abstract_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_domain_non_blocking_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_domain_test", -) - -syscall_test( - size = "medium", - add_overlay = True, - test = "//test/syscalls/linux:socket_filesystem_non_blocking_test", -) - -syscall_test( - size = "large", - add_overlay = True, - shard_count = most_shards, - test = "//test/syscalls/linux:socket_filesystem_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_inet_loopback_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - # Takes too long for TSAN. Creates a lot of TCP sockets. - tags = ["nogotsan"], - test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_ipv4_udp_unbound_external_networking_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test", -) - -syscall_test( - size = "medium", - add_hostinet = True, - test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_ip_tcp_loopback_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", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_ip_udp_loopback_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_ipv6_udp_unbound_loopback_test", -) - -syscall_test( - size = "medium", - add_hostinet = True, - shard_count = more_shards, - # Takes too long under gotsan to run. - tags = ["nogotsan"], - test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_nogotsan_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_netlink_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_ipv6_udp_unbound_loopback_netlink_test", -) - -syscall_test( - shard_count = more_shards, - test = "//test/syscalls/linux:socket_ip_unbound_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_ip_unbound_netlink_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_netdevice_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_netlink_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_netlink_route_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_netlink_uevent_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_blocking_local_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_blocking_ip_test", -) - -syscall_test( - add_hostinet = True, - test = "//test/syscalls/linux:socket_non_stream_blocking_local_test", -) - -syscall_test( - test = "//test/syscalls/linux:socket_non_stream_blocking_udp_test", -) - -syscall_test( - size = "large", - test = "//test/syscalls/linux:socket_stream_blocking_local_test", -) - -syscall_test( - size = "large", - test = "//test/syscalls/linux:socket_stream_blocking_tcp_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_stream_local_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_stream_nonblock_local_test", -) - -syscall_test( - # NOTE(b/116636318): Large sendmsg may stall a long time. - size = "enormous", - shard_count = more_shards, - test = "//test/syscalls/linux:socket_unix_dgram_local_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_unix_dgram_non_blocking_test", -) - -syscall_test( - size = "large", - add_overlay = True, - shard_count = most_shards, - test = "//test/syscalls/linux:socket_unix_pair_test", -) - -syscall_test( - # NOTE(b/116636318): Large sendmsg may stall a long time. - size = "enormous", - shard_count = more_shards, - test = "//test/syscalls/linux:socket_unix_seqpacket_local_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_unix_stream_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_unix_unbound_abstract_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_unix_unbound_dgram_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:socket_unix_unbound_filesystem_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test", -) - -syscall_test( - size = "large", - shard_count = most_shards, - test = "//test/syscalls/linux:socket_unix_unbound_stream_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:statfs_test", - use_tmpfs = True, # Test specifically relies on TEST_TMPDIR to be tmpfs. -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:stat_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:stat_times_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:sticky_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:symlink_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:sync_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:sync_file_range_test", -) - -syscall_test( - test = "//test/syscalls/linux:sysinfo_test", -) - -syscall_test( - test = "//test/syscalls/linux:syslog_test", -) - -syscall_test( - test = "//test/syscalls/linux:sysret_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:tcp_socket_test", -) - -syscall_test( - test = "//test/syscalls/linux:tgkill_test", -) - -syscall_test( - shard_count = more_shards, - test = "//test/syscalls/linux:timerfd_test", -) - -syscall_test( - test = "//test/syscalls/linux:timers_test", -) - -syscall_test( - test = "//test/syscalls/linux:time_test", -) - -syscall_test( - test = "//test/syscalls/linux:tkill_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:truncate_test", -) - -syscall_test( - test = "//test/syscalls/linux:tuntap_test", -) - -syscall_test( - add_hostinet = True, - test = "//test/syscalls/linux:tuntap_hostinet_test", -) - -syscall_test( - add_hostinet = True, - test = "//test/syscalls/linux:udp_bind_test", -) - -syscall_test( - size = "medium", - add_hostinet = True, - shard_count = more_shards, - test = "//test/syscalls/linux:udp_socket_test", -) - -syscall_test( - test = "//test/syscalls/linux:uidgid_test", -) - -syscall_test( - test = "//test/syscalls/linux:uname_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:unlink_test", -) - -syscall_test( - test = "//test/syscalls/linux:unshare_test", -) - -syscall_test( - test = "//test/syscalls/linux:utimes_test", -) - -syscall_test( - size = "medium", - test = "//test/syscalls/linux:vdso_clock_gettime_test", -) - -syscall_test( - test = "//test/syscalls/linux:vdso_test", -) - -syscall_test( - test = "//test/syscalls/linux:vsyscall_test", -) - -syscall_test( - test = "//test/syscalls/linux:vfork_test", -) - -syscall_test( - size = "medium", - shard_count = more_shards, - test = "//test/syscalls/linux:wait_test", -) - -syscall_test( - add_overlay = True, - test = "//test/syscalls/linux:write_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_net_unix_test", -) - -syscall_test( - add_hostinet = True, - test = "//test/syscalls/linux:proc_net_tcp_test", -) - -syscall_test( - test = "//test/syscalls/linux:proc_net_udp_test", -) - -syscall_test( - test = "//test/syscalls/linux:processes_test", -) diff --git a/test/syscalls/README.md b/test/syscalls/README.md deleted file mode 100644 index 9e0991940..000000000 --- a/test/syscalls/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# gVisor system call test suite - -This is a test suite for Linux system calls. It runs under both gVisor and -Linux, and ensures compatibility between the two. - -When adding support for a new syscall (or syscall argument) to gVisor, a -corresponding syscall test should be added. It's usually recommended to write -the test first and make sure that it passes on Linux before making changes to -gVisor. - -This document outlines the general guidelines for tests and specific rules that -must be followed for new tests. - -## Running the tests - -Each test file generates three different test targets that run in different -environments: - -* a `native` target that runs directly on the host machine, -* a `runsc_ptrace` target that runs inside runsc using the ptrace platform, and -* a `runsc_kvm` target that runs inside runsc using the KVM platform. - -For example, the test in `access_test.cc` generates the following targets: - -* `//test/syscalls:access_test_native` -* `//test/syscalls:access_test_runsc_ptrace` -* `//test/syscalls:access_test_runsc_kvm` - -Any of these targets can be run directly via `bazel test`. - -```bash -$ bazel test //test/syscalls:access_test_native -$ bazel test //test/syscalls:access_test_runsc_ptrace -$ bazel test //test/syscalls:access_test_runsc_kvm -``` - -To run all the tests on a particular platform, you can filter by the platform -tag: - -```bash -# Run all tests in native environment: -$ bazel test --test_tag_filters=native //test/syscalls/... - -# Run all tests in runsc with ptrace: -$ bazel test --test_tag_filters=runsc_ptrace //test/syscalls/... - -# Run all tests in runsc with kvm: -$ bazel test --test_tag_filters=runsc_kvm //test/syscalls/... -``` - -You can also run all the tests on every platform. (Warning, this may take a -while to run.) - -```bash -# Run all tests on every platform: -$ bazel test //test/syscalls/... -``` - -## Writing new tests - -Whenever we add support for a new syscall, or add support for a new argument or -option for a syscall, we should always add a new test (perhaps many new tests). - -In general, it is best to write the test first and make sure it passes on Linux -by running the test on the `native` platform on a Linux machine. This ensures -that the gVisor implementation matches actual Linux behavior. Sometimes man -pages contain errors, so always check the actual Linux behavior. - -gVisor uses the [Google Test][googletest] test framework, with a few custom -matchers and guidelines, described below. - -### Syscall matchers - -When testing an individual system call, use the following syscall matchers, -which will match the value returned by the syscall and the errno. - -```cc -SyscallSucceeds() -SyscallSucceedsWithValue(...) -SyscallFails() -SyscallFailsWithErrno(...) -``` - -### Use test utilities (RAII classes) - -The test utilties are written as RAII classes. These utilities should be -preferred over custom test harnesses. - -Local class instances should be preferred, wherever possible, over full test -fixtures. - -A test utility should be created when there is more than one test that requires -that same functionality, otherwise the class should be test local. - -## Save/Restore support in tests - -gVisor supports save/restore, and our syscall tests are written in a way to -enable saving/restoring at certain points. Hence, there are calls to -`MaybeSave`, and certain tests that should not trigger saves are named with -`NoSave`. - -However, the current open-source test runner does not yet support triggering -save/restore, so these functions and annotations have no effect on the tests. We -plan on extending the test runner to trigger save/restore. Until then, these -functions and annotations should be ignored. - -[googletest]: https://github.com/abseil/googletest diff --git a/test/syscalls/linux/32bit.cc b/test/syscalls/linux/32bit.cc deleted file mode 100644 index 3c825477c..000000000 --- a/test/syscalls/linux/32bit.cc +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <string.h> -#include <sys/mman.h> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/memory_util.h" -#include "test/util/platform_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -#ifndef __x86_64__ -#error "This test is x86-64 specific." -#endif - -namespace gvisor { -namespace testing { - -namespace { - -constexpr char kInt3 = '\xcc'; -constexpr char kInt80[2] = {'\xcd', '\x80'}; -constexpr char kSyscall[2] = {'\x0f', '\x05'}; -constexpr char kSysenter[2] = {'\x0f', '\x34'}; - -void ExitGroup32(const char instruction[2], int code) { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0)); - - // Fill with INT 3 in case we execute too far. - memset(m.ptr(), kInt3, m.len()); - - // Copy in the actual instruction. - memcpy(m.ptr(), instruction, 2); - - // We're playing *extremely* fast-and-loose with the various syscall ABIs - // here, which we can more-or-less get away with since exit_group doesn't - // return. - // - // SYSENTER expects the user stack in (%ebp) and arg6 in 0(%ebp). The kernel - // will unconditionally dereference %ebp for arg6, so we must pass a valid - // address or it will return EFAULT. - // - // SYSENTER also unconditionally returns to thread_info->sysenter_return which - // is ostensibly a stub in the 32-bit VDSO. But a 64-bit binary doesn't have - // the 32-bit VDSO mapped, so sysenter_return will simply be the value - // inherited from the most recent 32-bit ancestor, or NULL if there is none. - // As a result, return would not return from SYSENTER. - asm volatile( - "movl $252, %%eax\n" // exit_group - "movl %[code], %%ebx\n" // code - "movl %%edx, %%ebp\n" // SYSENTER: user stack (use IP as a valid addr) - "leaq -20(%%rsp), %%rsp\n" - "movl $0x2b, 16(%%rsp)\n" // SS = CPL3 data segment - "movl $0,12(%%rsp)\n" // ESP = nullptr (unused) - "movl $0, 8(%%rsp)\n" // EFLAGS - "movl $0x23, 4(%%rsp)\n" // CS = CPL3 32-bit code segment - "movl %%edx, 0(%%rsp)\n" // EIP - "iretl\n" - "int $3\n" - : - : [ code ] "m"(code), [ ip ] "d"(m.ptr()) - : "rax", "rbx"); -} - -constexpr int kExitCode = 42; - -TEST(Syscall32Bit, Int80) { - switch (PlatformSupport32Bit()) { - case PlatformSupport::NotSupported: - break; - case PlatformSupport::Segfault: - EXPECT_EXIT(ExitGroup32(kInt80, kExitCode), - ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Ignored: - // Since the call is ignored, we'll hit the int3 trap. - EXPECT_EXIT(ExitGroup32(kInt80, kExitCode), - ::testing::KilledBySignal(SIGTRAP), ""); - break; - - case PlatformSupport::Allowed: - EXPECT_EXIT(ExitGroup32(kInt80, kExitCode), ::testing::ExitedWithCode(42), - ""); - break; - } -} - -TEST(Syscall32Bit, Sysenter) { - if ((PlatformSupport32Bit() == PlatformSupport::Allowed || - PlatformSupport32Bit() == PlatformSupport::Ignored) && - GetCPUVendor() == CPUVendor::kAMD) { - // SYSENTER is an illegal instruction in compatibility mode on AMD. - EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), - ::testing::KilledBySignal(SIGILL), ""); - return; - } - - switch (PlatformSupport32Bit()) { - case PlatformSupport::NotSupported: - break; - - case PlatformSupport::Segfault: - EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), - ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Ignored: - // See above, except expected code is SIGSEGV. - EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), - ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Allowed: - EXPECT_EXIT(ExitGroup32(kSysenter, kExitCode), - ::testing::ExitedWithCode(42), ""); - break; - } -} - -TEST(Syscall32Bit, Syscall) { - if ((PlatformSupport32Bit() == PlatformSupport::Allowed || - PlatformSupport32Bit() == PlatformSupport::Ignored) && - GetCPUVendor() == CPUVendor::kIntel) { - // SYSCALL is an illegal instruction in compatibility mode on Intel. - EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), - ::testing::KilledBySignal(SIGILL), ""); - return; - } - - switch (PlatformSupport32Bit()) { - case PlatformSupport::NotSupported: - break; - - case PlatformSupport::Segfault: - EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), - ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Ignored: - // See above. - EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), - ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Allowed: - EXPECT_EXIT(ExitGroup32(kSyscall, kExitCode), - ::testing::ExitedWithCode(42), ""); - break; - } -} - -// Far call code called below. -// -// Input stack layout: -// -// %esp+12 lcall segment -// %esp+8 lcall address offset -// %esp+0 return address -// -// The lcall will enter compatibility mode and jump to the call address (the -// address of the lret). The lret will return to 64-bit mode at the retq, which -// will return to the external caller of this function. -// -// Since this enters compatibility mode, it must be mapped in a 32-bit region of -// address space and have a 32-bit stack pointer. -constexpr char kFarCall[] = { - '\x67', '\xff', '\x5c', '\x24', '\x08', // lcall *8(%esp) - '\xc3', // retq - '\xcb', // lret -}; - -void FarCall32() { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0)); - - // Fill with INT 3 in case we execute too far. - memset(m.ptr(), kInt3, m.len()); - - // 32-bit code. - memcpy(m.ptr(), kFarCall, sizeof(kFarCall)); - - // Use the end of the code page as its stack. - uintptr_t stack = m.endaddr(); - - uintptr_t lcall = m.addr(); - uintptr_t lret = m.addr() + sizeof(kFarCall) - 1; - - // N.B. We must save and restore RSP manually. GCC can do so automatically - // with an "rsp" clobber, but clang cannot. - asm volatile( - // Place the address of lret (%edx) and the 32-bit code segment (0x23) on - // the 32-bit stack for lcall. - "subl $0x8, %%ecx\n" - "movl $0x23, 4(%%ecx)\n" - "movl %%edx, 0(%%ecx)\n" - - // Save the current stack and switch to 32-bit stack. - "pushq %%rbp\n" - "movq %%rsp, %%rbp\n" - "movq %%rcx, %%rsp\n" - - // Run the lcall code. - "callq *%%rbx\n" - - // Restore the old stack. - "leaveq\n" - : "+c"(stack) - : "b"(lcall), "d"(lret)); -} - -TEST(Call32Bit, Disallowed) { - switch (PlatformSupport32Bit()) { - case PlatformSupport::NotSupported: - break; - - case PlatformSupport::Segfault: - EXPECT_EXIT(FarCall32(), ::testing::KilledBySignal(SIGSEGV), ""); - break; - - case PlatformSupport::Ignored: - ABSL_FALLTHROUGH_INTENDED; - case PlatformSupport::Allowed: - // Shouldn't crash. - FarCall32(); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD deleted file mode 100644 index 043ada583..000000000 --- a/test/syscalls/linux/BUILD +++ /dev/null @@ -1,4207 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "cc_library", "default_net_util", "gtest", "select_arch", "select_system") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -exports_files( - [ - "socket.cc", - "socket_inet_loopback.cc", - "socket_ip_loopback_blocking.cc", - "socket_ip_tcp_generic_loopback.cc", - "socket_ip_tcp_loopback.cc", - "socket_ip_tcp_loopback_blocking.cc", - "socket_ip_tcp_loopback_nonblock.cc", - "socket_ip_tcp_udp_generic.cc", - "socket_ip_udp_loopback.cc", - "socket_ip_udp_loopback_blocking.cc", - "socket_ip_udp_loopback_nonblock.cc", - "socket_ip_unbound.cc", - "socket_ipv4_udp_unbound_external_networking_test.cc", - "socket_ipv6_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", - "udp_socket.cc", - ], - visibility = ["//:sandbox"], -) - -cc_binary( - name = "sigaltstack_check", - testonly = 1, - srcs = ["sigaltstack_check.cc"], - deps = ["//test/util:logging"], -) - -cc_binary( - name = "exec_assert_closed_workload", - testonly = 1, - srcs = ["exec_assert_closed_workload.cc"], - deps = [ - "@com_google_absl//absl/strings", - ], -) - -cc_binary( - name = "exec_basic_workload", - testonly = 1, - srcs = [ - "exec.h", - "exec_basic_workload.cc", - ], -) - -cc_binary( - name = "exec_proc_exe_workload", - testonly = 1, - srcs = ["exec_proc_exe_workload.cc"], - deps = [ - "//test/util:fs_util", - "//test/util:posix_error", - ], -) - -cc_binary( - name = "exec_state_workload", - testonly = 1, - srcs = ["exec_state_workload.cc"], - deps = ["@com_google_absl//absl/strings"], -) - -sh_binary( - name = "exit_script", - testonly = 1, - srcs = [ - "exit_script.sh", - ], -) - -cc_binary( - name = "priority_execve", - testonly = 1, - srcs = [ - "priority_execve.cc", - ], -) - -cc_library( - name = "base_poll_test", - testonly = 1, - srcs = ["base_poll_test.cc"], - hdrs = ["base_poll_test.h"], - deps = [ - "@com_google_absl//absl/memory", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_library( - name = "file_base", - testonly = 1, - hdrs = ["file_base.h"], - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_util", - ], -) - -cc_library( - name = "socket_netlink_util", - testonly = 1, - srcs = ["socket_netlink_util.cc"], - hdrs = ["socket_netlink_util.h"], - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "//test/util:posix_error", - "@com_google_absl//absl/strings", - ], -) - -cc_library( - name = "socket_netlink_route_util", - testonly = 1, - srcs = ["socket_netlink_route_util.cc"], - hdrs = ["socket_netlink_route_util.h"], - deps = [ - ":socket_netlink_util", - ], -) - -cc_library( - name = "socket_test_util", - testonly = 1, - srcs = [ - "socket_test_util.cc", - "socket_test_util_impl.cc", - ], - hdrs = ["socket_test_util.h"], - defines = select_system(), - deps = default_net_util() + [ - gtest, - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/time", - "@com_google_absl//absl/types:optional", - "//test/util:file_descriptor", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_library( - name = "unix_domain_socket_test_util", - testonly = 1, - srcs = ["unix_domain_socket_test_util.cc"], - hdrs = ["unix_domain_socket_test_util.h"], - deps = [ - ":socket_test_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_util", - ], -) - -cc_library( - name = "ip_socket_test_util", - testonly = 1, - srcs = ["ip_socket_test_util.cc"], - hdrs = ["ip_socket_test_util.h"], - deps = [ - ":socket_test_util", - "@com_google_absl//absl/strings", - ], -) - -cc_binary( - name = "clock_nanosleep_test", - testonly = 1, - srcs = ["clock_nanosleep.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "32bit_test", - testonly = 1, - srcs = select_arch( - amd64 = ["32bit.cc"], - arm64 = [], - ), - linkstatic = 1, - deps = [ - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:memory_util", - "//test/util:platform_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "accept_bind_test", - testonly = 1, - srcs = ["accept_bind.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "accept_bind_stream_test", - testonly = 1, - srcs = ["accept_bind_stream.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "access_test", - testonly = 1, - srcs = ["access.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:fs_util", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "affinity_test", - testonly = 1, - srcs = ["affinity.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "aio_test", - testonly = 1, - srcs = [ - "aio.cc", - "file_base.h", - ], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "alarm_test", - testonly = 1, - srcs = ["alarm.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "bad_test", - testonly = 1, - srcs = ["bad.cc"], - linkstatic = 1, - visibility = [ - "//:sandbox", - ], - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "bind_test", - testonly = 1, - srcs = ["bind.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_test", - testonly = 1, - srcs = ["socket.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - gtest, - "//test/util:file_descriptor", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_capability_test", - testonly = 1, - srcs = ["socket_capability.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "brk_test", - testonly = 1, - srcs = ["brk.cc"], - linkstatic = 1, - deps = [ - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "chdir_test", - testonly = 1, - srcs = ["chdir.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "chmod_test", - testonly = 1, - srcs = ["chmod.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "chown_test", - testonly = 1, - srcs = ["chown.cc"], - linkstatic = 1, - # We require additional UIDs for this test, so don't include the bazel - # sandbox as standard. - tags = ["no-sandbox"], - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/synchronization", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "sticky_test", - testonly = 1, - srcs = ["sticky.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/flags:flag", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "chroot_test", - testonly = 1, - srcs = ["chroot.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:logging", - "//test/util:mount_util", - "//test/util:multiprocess_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "clock_getres_test", - testonly = 1, - srcs = ["clock_getres.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "clock_gettime_test", - testonly = 1, - srcs = ["clock_gettime.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "concurrency_test", - testonly = 1, - srcs = ["concurrency.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:platform_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "connect_external_test", - testonly = 1, - srcs = ["connect_external.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "creat_test", - testonly = 1, - srcs = ["creat.cc"], - linkstatic = 1, - deps = [ - "//test/util:fs_util", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "dev_test", - testonly = 1, - srcs = ["dev.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "dup_test", - testonly = 1, - srcs = ["dup.cc"], - linkstatic = 1, - deps = [ - "//test/util:eventfd_util", - "//test/util:file_descriptor", - gtest, - "//test/util:fs_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "epoll_test", - testonly = 1, - srcs = ["epoll.cc"], - linkstatic = 1, - deps = [ - "//test/util:epoll_util", - "//test/util:eventfd_util", - "//test/util:file_descriptor", - gtest, - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "eventfd_test", - testonly = 1, - srcs = ["eventfd.cc"], - linkstatic = 1, - deps = [ - "//test/util:epoll_util", - "//test/util:eventfd_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "exceptions_test", - testonly = 1, - srcs = ["exceptions.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:logging", - "//test/util:platform_util", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "getcpu_test", - testonly = 1, - srcs = ["getcpu.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "getcpu_host_test", - testonly = 1, - srcs = ["getcpu.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "getrusage_test", - testonly = 1, - srcs = ["getrusage.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "exec_binary_test", - testonly = 1, - srcs = ["exec_binary.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "exec_test", - testonly = 1, - srcs = [ - "exec.cc", - "exec.h", - ], - data = [ - ":exec_assert_closed_workload", - ":exec_basic_workload", - ":exec_proc_exe_workload", - ":exec_state_workload", - ":exit_script", - ":priority_execve", - ], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/types:optional", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "exit_test", - testonly = 1, - srcs = ["exit.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:time_util", - ], -) - -cc_binary( - name = "fallocate_test", - testonly = 1, - srcs = ["fallocate.cc"], - linkstatic = 1, - deps = [ - ":file_base", - ":socket_test_util", - "//test/util:cleanup", - "//test/util:eventfd_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "fault_test", - testonly = 1, - srcs = ["fault.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "fchdir_test", - testonly = 1, - srcs = ["fchdir.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "fcntl_test", - testonly = 1, - srcs = ["fcntl.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:eventfd_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:signal_util", - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "flock_test", - testonly = 1, - srcs = [ - "file_base.h", - "flock.cc", - ], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:epoll_util", - "//test/util:eventfd_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "fork_test", - testonly = 1, - srcs = ["fork.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "fpsig_fork_test", - testonly = 1, - srcs = ["fpsig_fork.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "fpsig_nested_test", - testonly = 1, - srcs = ["fpsig_nested.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "sync_file_range_test", - testonly = 1, - srcs = ["sync_file_range.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "fsync_test", - testonly = 1, - srcs = ["fsync.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "futex_test", - testonly = 1, - srcs = ["futex.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/time", - gtest, - "//test/util:memory_util", - "//test/util:save_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:time_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "getdents_test", - testonly = 1, - srcs = ["getdents.cc"], - linkstatic = 1, - deps = [ - "//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, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "getrandom_test", - testonly = 1, - srcs = ["getrandom.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "inotify_test", - testonly = 1, - srcs = ["inotify.cc"], - linkstatic = 1, - deps = [ - "//test/util:epoll_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "ioctl_test", - testonly = 1, - srcs = ["ioctl.cc"], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_library( - name = "iptables_types", - testonly = 1, - hdrs = [ - "iptables.h", - ], -) - -cc_binary( - name = "iptables_test", - testonly = 1, - srcs = [ - "iptables.cc", - ], - linkstatic = 1, - deps = [ - ":iptables_types", - ":socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "ip6tables_test", - testonly = 1, - srcs = [ - "ip6tables.cc", - ], - linkstatic = 1, - deps = [ - ":iptables_types", - ":socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "itimer_test", - testonly = 1, - srcs = ["itimer.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "kcov_test", - testonly = 1, - srcs = ["kcov.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "kill_test", - testonly = 1, - srcs = ["kill.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "link_test", - testonly = 1, - srcs = ["link.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "lseek_test", - testonly = 1, - srcs = ["lseek.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "madvise_test", - testonly = 1, - srcs = ["madvise.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "membarrier_test", - testonly = 1, - srcs = ["membarrier.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:cleanup", - "//test/util:logging", - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "mempolicy_test", - testonly = 1, - srcs = ["mempolicy.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "@com_google_absl//absl/memory", - gtest, - "//test/util:memory_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "mincore_test", - testonly = 1, - srcs = ["mincore.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mkdir_test", - testonly = 1, - srcs = ["mkdir.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:fs_util", - gtest, - "//test/util:temp_path", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mknod_test", - testonly = 1, - srcs = ["mknod.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "mlock_test", - testonly = 1, - srcs = ["mlock.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - gtest, - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:rlimit_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mmap_test", - testonly = 1, - srcs = ["mmap.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "mount_test", - testonly = 1, - srcs = ["mount.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:mount_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "mremap_test", - testonly = 1, - srcs = ["mremap.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "msync_test", - testonly = 1, - srcs = ["msync.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "munmap_test", - testonly = 1, - srcs = ["munmap.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "open_test", - testonly = 1, - srcs = [ - "file_base.h", - "open.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "open_create_test", - testonly = 1, - srcs = ["open_create.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:temp_umask", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "packet_socket_raw_test", - testonly = 1, - srcs = ["packet_socket_raw.cc"], - defines = select_system(), - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/base:endian", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "packet_socket_test", - testonly = 1, - srcs = ["packet_socket.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/base:endian", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "pty_test", - testonly = 1, - srcs = ["pty.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:cleanup", - "//test/util:posix_error", - "//test/util:pty_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "pty_root_test", - testonly = 1, - srcs = ["pty_root.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:posix_error", - "//test/util:pty_util", - "//test/util:test_main", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "partial_bad_buffer_test", - testonly = 1, - srcs = ["partial_bad_buffer.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "pause_test", - testonly = 1, - srcs = ["pause.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "ping_socket_test", - testonly = 1, - srcs = ["ping_socket.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:save_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "pipe_test", - testonly = 1, - srcs = ["pipe.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "poll_test", - testonly = 1, - srcs = ["poll.cc"], - linkstatic = 1, - deps = [ - ":base_poll_test", - "//test/util:eventfd_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "ppoll_test", - testonly = 1, - srcs = ["ppoll.cc"], - linkstatic = 1, - deps = [ - ":base_poll_test", - "@com_google_absl//absl/time", - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "arch_prctl_test", - testonly = 1, - srcs = select_arch( - amd64 = ["arch_prctl.cc"], - arm64 = [], - ), - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "prctl_test", - testonly = 1, - srcs = ["prctl.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "@com_google_absl//absl/flags:flag", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "prctl_setuid_test", - testonly = 1, - srcs = ["prctl_setuid.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "@com_google_absl//absl/flags:flag", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "pread64_test", - testonly = 1, - srcs = ["pread64.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "preadv_test", - testonly = 1, - srcs = ["preadv.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "preadv2_test", - testonly = 1, - srcs = [ - "file_base.h", - "preadv2.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "priority_test", - testonly = 1, - srcs = ["priority.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "proc_test", - testonly = 1, - srcs = ["proc.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/container:node_hash_set", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:time_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "proc_net_test", - testonly = 1, - srcs = ["proc_net.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "proc_pid_oomscore_test", - testonly = 1, - srcs = ["proc_pid_oomscore.cc"], - linkstatic = 1, - deps = [ - "//test/util:fs_util", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/strings", - ], -) - -cc_binary( - name = "proc_pid_smaps_test", - testonly = 1, - srcs = ["proc_pid_smaps.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:optional", - gtest, - "//test/util:memory_util", - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "proc_pid_uid_gid_map_test", - testonly = 1, - srcs = ["proc_pid_uid_gid_map.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:time_util", - ], -) - -cc_binary( - name = "pselect_test", - testonly = 1, - srcs = ["pselect.cc"], - linkstatic = 1, - deps = [ - ":base_poll_test", - "@com_google_absl//absl/time", - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "ptrace_test", - testonly = 1, - srcs = ["ptrace.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/time", - gtest, - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "//test/util:logging", - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:platform_util", - "//test/util:signal_util", - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:time_util", - ], -) - -cc_binary( - name = "pwrite64_test", - testonly = 1, - srcs = ["pwrite64.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "pwritev2_test", - testonly = 1, - srcs = [ - "pwritev2.cc", - ], - linkstatic = 1, - deps = [ - ":file_base", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "raw_socket_hdrincl_test", - testonly = 1, - srcs = ["raw_socket_hdrincl.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/base:endian", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "raw_socket_test", - testonly = 1, - srcs = ["raw_socket.cc"], - defines = select_system(), - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "raw_socket_icmp_test", - testonly = 1, - srcs = ["raw_socket_icmp.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "read_test", - testonly = 1, - srcs = ["read.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:cleanup", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "readahead_test", - testonly = 1, - srcs = ["readahead.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "readv_test", - testonly = 1, - srcs = [ - "file_base.h", - "readv.cc", - "readv_common.cc", - "readv_common.h", - ], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "readv_socket_test", - testonly = 1, - srcs = [ - "readv_common.cc", - "readv_common.h", - "readv_socket.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "rename_test", - testonly = 1, - srcs = ["rename.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "rlimits_test", - testonly = 1, - srcs = ["rlimits.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "rseq_test", - testonly = 1, - srcs = ["rseq.cc"], - data = ["//test/syscalls/linux/rseq"], - linkstatic = 1, - deps = [ - "//test/syscalls/linux/rseq:lib", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "rtsignal_test", - testonly = 1, - srcs = ["rtsignal.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - gtest, - "//test/util:logging", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sched_test", - testonly = 1, - srcs = ["sched.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sched_yield_test", - testonly = 1, - srcs = ["sched_yield.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "seccomp_test", - testonly = 1, - srcs = ["seccomp.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:logging", - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "select_test", - testonly = 1, - srcs = ["select.cc"], - linkstatic = 1, - deps = [ - ":base_poll_test", - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:rlimit_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sendfile_test", - testonly = 1, - srcs = ["sendfile.cc"], - linkstatic = 1, - deps = [ - "//test/util:eventfd_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:signal_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "sendfile_socket_test", - testonly = 1, - srcs = ["sendfile_socket.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - ":ip_socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "setgid_test", - testonly = 1, - srcs = ["setgid.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:fs_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/strings", - gtest, - ], -) - -cc_binary( - name = "splice_test", - testonly = 1, - srcs = ["splice.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:signal_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "sigaction_test", - testonly = 1, - srcs = ["sigaction.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sigaltstack_test", - testonly = 1, - srcs = ["sigaltstack.cc"], - data = [ - ":sigaltstack_check", - ], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:fs_util", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "sigreturn_test", - testonly = 1, - srcs = select_arch( - amd64 = ["sigreturn_amd64.cc"], - arm64 = ["sigreturn_arm64.cc"], - ), - linkstatic = 1, - deps = [ - gtest, - "//test/util:logging", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:timer_util", - ] + select_arch( - amd64 = [], - arm64 = ["//test/util:test_main"], - ), -) - -cc_binary( - name = "signalfd_test", - testonly = 1, - srcs = ["signalfd.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/synchronization", - gtest, - "//test/util:logging", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "sigprocmask_test", - testonly = 1, - srcs = ["sigprocmask.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sigstop_test", - testonly = 1, - srcs = ["sigstop.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/time", - gtest, - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "sigtimedwait_test", - testonly = 1, - srcs = ["sigtimedwait.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_library( - name = "socket_generic_test_cases", - testonly = 1, - srcs = [ - "socket_generic_test_cases.cc", - ], - hdrs = [ - "socket_generic.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_binary( - name = "socket_stress_test", - testonly = 1, - srcs = [ - "socket_generic_stress.cc", - ], - linkstatic = 1, - deps = [ - gtest, - ":ip_socket_test_util", - ":socket_test_util", - "//test/util:file_descriptor", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - ], -) - -cc_library( - name = "socket_unix_dgram_test_cases", - testonly = 1, - srcs = ["socket_unix_dgram.cc"], - hdrs = ["socket_unix_dgram.h"], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_unix_seqpacket_test_cases", - testonly = 1, - srcs = ["socket_unix_seqpacket.cc"], - hdrs = ["socket_unix_seqpacket.h"], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ip_tcp_generic_test_cases", - testonly = 1, - srcs = [ - "socket_ip_tcp_generic.cc", - ], - hdrs = [ - "socket_ip_tcp_generic.h", - ], - deps = [ - ":socket_test_util", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/time", - gtest, - "//test/util:temp_path", - "//test/util:test_util", - "//test/util:thread_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_non_blocking_test_cases", - testonly = 1, - srcs = [ - "socket_non_blocking.cc", - ], - hdrs = [ - "socket_non_blocking.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_unix_non_stream_test_cases", - testonly = 1, - srcs = [ - "socket_unix_non_stream.cc", - ], - hdrs = [ - "socket_unix_non_stream.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:memory_util", - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_non_stream_test_cases", - testonly = 1, - srcs = [ - "socket_non_stream.cc", - ], - hdrs = [ - "socket_non_stream.h", - ], - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ip_udp_test_cases", - testonly = 1, - srcs = [ - "socket_ip_udp_generic.cc", - ], - hdrs = [ - "socket_ip_udp_generic.h", - ], - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ipv4_udp_unbound_test_cases", - testonly = 1, - srcs = [ - "socket_ipv4_udp_unbound.cc", - ], - hdrs = [ - "socket_ipv4_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_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 = [ - "socket_ipv4_udp_unbound_netlink.cc", - ], - hdrs = [ - "socket_ipv4_udp_unbound_netlink.h", - ], - deps = [ - ":socket_netlink_route_util", - ":socket_test_util", - "//test/util:capability_util", - "//test/util:cleanup", - gtest, - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ipv6_udp_unbound_netlink_test_cases", - testonly = 1, - srcs = [ - "socket_ipv6_udp_unbound_netlink.cc", - ], - hdrs = [ - "socket_ipv6_udp_unbound_netlink.h", - ], - deps = [ - ":socket_netlink_route_util", - ":socket_test_util", - "//test/util:capability_util", - gtest, - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ip_udp_unbound_external_networking", - testonly = 1, - srcs = [ - "socket_ip_udp_unbound_external_networking.cc", - ], - hdrs = [ - "socket_ip_udp_unbound_external_networking.h", - ], - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ipv4_udp_unbound_external_networking_test_cases", - testonly = 1, - srcs = [ - "socket_ipv4_udp_unbound_external_networking.cc", - ], - hdrs = [ - "socket_ipv4_udp_unbound_external_networking.h", - ], - deps = [ - ":socket_ip_udp_unbound_external_networking", - gtest, - ], - alwayslink = 1, -) - -cc_library( - name = "socket_ipv6_udp_unbound_external_networking_test_cases", - testonly = 1, - srcs = [ - "socket_ipv6_udp_unbound_external_networking.cc", - ], - hdrs = [ - "socket_ipv6_udp_unbound_external_networking.h", - ], - deps = [ - ":socket_ip_udp_unbound_external_networking", - gtest, - ], - alwayslink = 1, -) - -cc_binary( - name = "socket_abstract_test", - testonly = 1, - srcs = [ - "socket_abstract.cc", - ], - linkstatic = 1, - deps = [ - ":socket_generic_test_cases", - ":socket_test_util", - ":socket_unix_cmsg_test_cases", - ":socket_unix_test_cases", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_abstract_non_blocking_test", - testonly = 1, - srcs = [ - "socket_unix_abstract_nonblock.cc", - ], - linkstatic = 1, - deps = [ - ":socket_non_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_dgram_local_test", - testonly = 1, - srcs = ["socket_unix_dgram_local.cc"], - linkstatic = 1, - deps = [ - ":socket_non_stream_test_cases", - ":socket_test_util", - ":socket_unix_dgram_test_cases", - ":socket_unix_non_stream_test_cases", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_dgram_non_blocking_test", - testonly = 1, - srcs = ["socket_unix_dgram_non_blocking.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_seqpacket_local_test", - testonly = 1, - srcs = [ - "socket_unix_seqpacket_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_non_stream_test_cases", - ":socket_test_util", - ":socket_unix_non_stream_test_cases", - ":socket_unix_seqpacket_test_cases", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_stream_test", - testonly = 1, - srcs = ["socket_unix_stream.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_tcp_generic_loopback_test", - testonly = 1, - srcs = [ - "socket_ip_tcp_generic_loopback.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ip_tcp_generic_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_tcp_udp_generic_loopback_test", - testonly = 1, - srcs = [ - "socket_ip_tcp_udp_generic.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_tcp_loopback_test", - testonly = 1, - srcs = [ - "socket_ip_tcp_loopback.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_generic_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_tcp_loopback_non_blocking_test", - testonly = 1, - srcs = [ - "socket_ip_tcp_loopback_nonblock.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_non_blocking_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_udp_loopback_test", - testonly = 1, - srcs = [ - "socket_ip_udp_loopback.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_generic_test_cases", - ":socket_ip_udp_test_cases", - ":socket_non_stream_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ipv4_udp_unbound_external_networking_test", - testonly = 1, - srcs = [ - "socket_ipv4_udp_unbound_external_networking_test.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ipv4_udp_unbound_external_networking_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ipv6_udp_unbound_external_networking_test", - testonly = 1, - srcs = [ - "socket_ipv6_udp_unbound_external_networking_test.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ipv6_udp_unbound_external_networking_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_bind_to_device_test", - testonly = 1, - srcs = [ - "socket_bind_to_device.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_bind_to_device_util", - ":socket_test_util", - "//test/util:capability_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "socket_bind_to_device_sequence_test", - testonly = 1, - srcs = [ - "socket_bind_to_device_sequence.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_bind_to_device_util", - ":socket_test_util", - "//test/util:capability_util", - "@com_google_absl//absl/container:node_hash_map", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "socket_bind_to_device_distribution_test", - testonly = 1, - srcs = [ - "socket_bind_to_device_distribution.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_bind_to_device_util", - ":socket_test_util", - "//test/util:capability_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "socket_ip_udp_loopback_non_blocking_test", - testonly = 1, - srcs = [ - "socket_ip_udp_loopback_nonblock.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_non_blocking_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ipv4_udp_unbound_loopback_test", - testonly = 1, - srcs = [ - "socket_ipv4_udp_unbound_loopback.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ipv4_udp_unbound_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -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 = [ - "socket_ipv4_udp_unbound_loopback_nogotsan.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/memory", - ], -) - -cc_binary( - name = "socket_ipv4_udp_unbound_loopback_netlink_test", - testonly = 1, - srcs = [ - "socket_ipv4_udp_unbound_loopback_netlink.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ipv4_udp_unbound_netlink_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ipv6_udp_unbound_loopback_netlink_test", - testonly = 1, - srcs = [ - "socket_ipv6_udp_unbound_loopback_netlink.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_ipv6_udp_unbound_netlink_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_unbound_test", - testonly = 1, - srcs = [ - "socket_ip_unbound.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_ip_unbound_netlink_test", - testonly = 1, - srcs = [ - "socket_ip_unbound_netlink.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_netlink_route_util", - ":socket_test_util", - "//test/util:capability_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_domain_test", - testonly = 1, - srcs = [ - "socket_unix_domain.cc", - ], - linkstatic = 1, - deps = [ - ":socket_generic_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_domain_non_blocking_test", - testonly = 1, - srcs = [ - "socket_unix_pair_nonblock.cc", - ], - linkstatic = 1, - deps = [ - ":socket_non_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_filesystem_test", - testonly = 1, - srcs = [ - "socket_filesystem.cc", - ], - linkstatic = 1, - deps = [ - ":socket_generic_test_cases", - ":socket_test_util", - ":socket_unix_cmsg_test_cases", - ":socket_unix_test_cases", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_filesystem_non_blocking_test", - testonly = 1, - srcs = [ - "socket_unix_filesystem_nonblock.cc", - ], - linkstatic = 1, - deps = [ - ":socket_non_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_inet_loopback_test", - testonly = 1, - srcs = ["socket_inet_loopback.cc"], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "socket_inet_loopback_nogotsan_test", - testonly = 1, - srcs = ["socket_inet_loopback_nogotsan.cc"], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "socket_netlink_test", - testonly = 1, - srcs = ["socket_netlink.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_netlink_route_test", - testonly = 1, - srcs = ["socket_netlink_route.cc"], - linkstatic = 1, - deps = [ - ":socket_netlink_route_util", - ":socket_netlink_util", - ":socket_test_util", - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings:str_format", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_netlink_uevent_test", - testonly = 1, - srcs = ["socket_netlink_uevent.cc"], - linkstatic = 1, - deps = [ - ":socket_netlink_util", - ":socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -# These socket tests are in a library because the test cases are shared -# across several test build targets. -cc_library( - name = "socket_stream_test_cases", - testonly = 1, - srcs = [ - "socket_stream.cc", - ], - hdrs = [ - "socket_stream.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_blocking_test_cases", - testonly = 1, - srcs = [ - "socket_blocking.cc", - ], - hdrs = [ - "socket_blocking.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_unix_test_cases", - testonly = 1, - srcs = [ - "socket_unix.cc", - ], - hdrs = [ - "socket_unix.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_util", - "//test/util:thread_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_unix_cmsg_test_cases", - testonly = 1, - srcs = [ - "socket_unix_cmsg.cc", - ], - hdrs = [ - "socket_unix_cmsg.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_util", - "//test/util:thread_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_stream_blocking_test_cases", - testonly = 1, - srcs = [ - "socket_stream_blocking.cc", - ], - hdrs = [ - "socket_stream_blocking.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_stream_nonblocking_test_cases", - testonly = 1, - srcs = [ - "socket_stream_nonblock.cc", - ], - hdrs = [ - "socket_stream_nonblock.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_non_stream_blocking_test_cases", - testonly = 1, - srcs = [ - "socket_non_stream_blocking.cc", - ], - hdrs = [ - "socket_non_stream_blocking.h", - ], - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_util", - "//test/util:thread_util", - ], - alwayslink = 1, -) - -cc_library( - name = "socket_bind_to_device_util", - testonly = 1, - srcs = [ - "socket_bind_to_device_util.cc", - ], - hdrs = [ - "socket_bind_to_device_util.h", - ], - deps = [ - "//test/util:test_util", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - ], - alwayslink = 1, -) - -cc_binary( - name = "socket_stream_local_test", - testonly = 1, - srcs = [ - "socket_unix_stream_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_stream_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_stream_blocking_local_test", - testonly = 1, - srcs = [ - "socket_unix_stream_blocking_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_stream_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_stream_blocking_tcp_test", - testonly = 1, - srcs = [ - "socket_ip_tcp_loopback_blocking.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_stream_blocking_test_cases", - ":socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_stream_nonblock_local_test", - testonly = 1, - srcs = [ - "socket_unix_stream_nonblock_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_stream_nonblocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_unbound_dgram_test", - testonly = 1, - srcs = ["socket_unix_unbound_dgram.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_unbound_abstract_test", - testonly = 1, - srcs = ["socket_unix_unbound_abstract.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_unbound_filesystem_test", - testonly = 1, - srcs = ["socket_unix_unbound_filesystem.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:file_descriptor", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_blocking_local_test", - testonly = 1, - srcs = [ - "socket_unix_blocking_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_blocking_ip_test", - testonly = 1, - srcs = [ - "socket_ip_loopback_blocking.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_blocking_test_cases", - ":socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_non_stream_blocking_local_test", - testonly = 1, - srcs = [ - "socket_unix_non_stream_blocking_local.cc", - ], - linkstatic = 1, - deps = [ - ":socket_non_stream_blocking_test_cases", - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_non_stream_blocking_udp_test", - testonly = 1, - srcs = [ - "socket_ip_udp_loopback_blocking.cc", - ], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_non_stream_blocking_test_cases", - ":socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_pair_test", - testonly = 1, - srcs = [ - "socket_unix_pair.cc", - ], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":socket_unix_cmsg_test_cases", - ":socket_unix_test_cases", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_unbound_seqpacket_test", - testonly = 1, - srcs = ["socket_unix_unbound_seqpacket.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_unix_unbound_stream_test", - testonly = 1, - srcs = ["socket_unix_unbound_stream.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - ":unix_domain_socket_test_util", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "socket_netdevice_test", - testonly = 1, - srcs = ["socket_netdevice.cc"], - linkstatic = 1, - deps = [ - ":socket_netlink_util", - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/base:endian", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "stat_test", - testonly = 1, - srcs = [ - "file_base.h", - "stat.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "stat_times_test", - testonly = 1, - srcs = ["stat_times.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "statfs_test", - testonly = 1, - srcs = [ - "file_base.h", - "statfs.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "symlink_test", - testonly = 1, - srcs = ["symlink.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/time", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sync_test", - testonly = 1, - srcs = ["sync.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sysinfo_test", - testonly = 1, - srcs = ["sysinfo.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "syslog_test", - testonly = 1, - srcs = ["syslog.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "sysret_test", - testonly = 1, - srcs = ["sysret.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "tcp_socket_test", - testonly = 1, - srcs = ["tcp_socket.cc"], - defines = select_system(), - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/time", - gtest, - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "tgkill_test", - testonly = 1, - srcs = ["tgkill.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "time_test", - testonly = 1, - srcs = ["time.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:proc_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "timerfd_test", - testonly = 1, - srcs = ["timerfd.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "timers_test", - testonly = 1, - srcs = ["timers.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:timer_util", - ], -) - -cc_binary( - name = "tkill_test", - testonly = 1, - srcs = ["tkill.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:logging", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "truncate_test", - testonly = 1, - srcs = ["truncate.cc"], - linkstatic = 1, - deps = [ - ":file_base", - "//test/util:capability_util", - "//test/util:cleanup", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "tuntap_test", - testonly = 1, - srcs = ["tuntap.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - gtest, - ":socket_netlink_route_util", - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/strings", - ], -) - -cc_binary( - name = "tuntap_hostinet_test", - testonly = 1, - srcs = ["tuntap_hostinet.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "udp_socket_test", - testonly = 1, - srcs = ["udp_socket.cc"], - defines = select_system(), - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - ":socket_test_util", - ":unix_domain_socket_test_util", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/time", - gtest, - "//test/util:file_descriptor", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "udp_bind_test", - testonly = 1, - srcs = ["udp_bind.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - "//test/util:file_descriptor", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "uidgid_test", - testonly = 1, - srcs = ["uidgid.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/strings", - gtest, - "//test/util:cleanup", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:uid_util", - ], -) - -cc_binary( - name = "uname_test", - testonly = 1, - srcs = ["uname.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "unlink_test", - testonly = 1, - srcs = ["unlink.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "unshare_test", - testonly = 1, - srcs = ["unshare.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/synchronization", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "utimes_test", - testonly = 1, - srcs = ["utimes.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:fs_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "vdso_test", - testonly = 1, - srcs = ["vdso.cc"], - linkstatic = 1, - deps = [ - "//test/util:fs_util", - gtest, - "//test/util:posix_error", - "//test/util:proc_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "vfork_test", - testonly = 1, - srcs = ["vfork.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:test_util", - "//test/util:time_util", - ], -) - -cc_binary( - name = "wait_test", - testonly = 1, - srcs = ["wait.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:logging", - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:signal_util", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - "//test/util:time_util", - ], -) - -cc_binary( - name = "write_test", - testonly = 1, - srcs = ["write.cc"], - linkstatic = 1, - deps = [ - "//test/util:cleanup", - "@com_google_absl//absl/base:core_headers", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "memory_accounting_test", - testonly = 1, - srcs = ["memory_accounting.cc"], - linkstatic = 1, - deps = [ - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - gtest, - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "network_namespace_test", - testonly = 1, - srcs = ["network_namespace.cc"], - linkstatic = 1, - deps = [ - ":socket_test_util", - gtest, - "//test/util:capability_util", - "//test/util:posix_error", - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "semaphore_test", - testonly = 1, - srcs = ["semaphore.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/synchronization", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - "//test/util:thread_util", - ], -) - -cc_binary( - name = "shm_test", - testonly = 1, - srcs = ["shm.cc"], - linkstatic = 1, - deps = [ - "//test/util:multiprocess_util", - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - "@com_google_absl//absl/time", - ], -) - -cc_binary( - name = "fadvise64_test", - testonly = 1, - srcs = ["fadvise64.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - gtest, - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "vdso_clock_gettime_test", - testonly = 1, - srcs = ["vdso_clock_gettime.cc"], - linkstatic = 1, - deps = [ - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "vsyscall_test", - testonly = 1, - srcs = ["vsyscall.cc"], - linkstatic = 1, - deps = [ - gtest, - "//test/util:proc_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "proc_net_unix_test", - testonly = 1, - srcs = ["proc_net_unix.cc"], - linkstatic = 1, - deps = [ - ":unix_domain_socket_test_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - gtest, - "//test/util:cleanup", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "memfd_test", - testonly = 1, - srcs = ["memfd.cc"], - linkstatic = 1, - deps = [ - "//test/util:file_descriptor", - "//test/util:fs_util", - gtest, - "//test/util:memory_util", - "//test/util:multiprocess_util", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "proc_net_tcp_test", - testonly = 1, - srcs = ["proc_net_tcp.cc"], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "proc_net_udp_test", - testonly = 1, - srcs = ["proc_net_udp.cc"], - linkstatic = 1, - deps = [ - ":ip_socket_test_util", - "//test/util:file_descriptor", - "@com_google_absl//absl/strings", - gtest, - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "processes_test", - testonly = 1, - srcs = ["processes.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_binary( - name = "xattr_test", - testonly = 1, - srcs = [ - "file_base.h", - "xattr.cc", - ], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:fs_util", - "@com_google_absl//absl/container:flat_hash_set", - "@com_google_absl//absl/strings", - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_main", - "//test/util:test_util", - ], -) diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc deleted file mode 100644 index f65a14fb8..000000000 --- a/test/syscalls/linux/accept_bind.cc +++ /dev/null @@ -1,641 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/un.h> - -#include <algorithm> -#include <vector> - -#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 { -namespace testing { - -namespace { - -TEST_P(AllSocketPairTest, Listen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, ListenIncreaseBacklog) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5), - SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 10), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, ListenDecreaseBacklog) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5), - SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 1), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, ListenWithoutBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(listen(sockets->first_fd(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, DoubleBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, BindListenBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, DoubleListen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, DoubleConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(EISCONN)); -} - -TEST_P(AllSocketPairTest, Connect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, ConnectWithWrongType) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int type; - socklen_t typelen = sizeof(type); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_TYPE, &type, &typelen), - SyscallSucceeds()); - switch (type) { - case SOCK_STREAM: - type = SOCK_SEQPACKET; - break; - case SOCK_SEQPACKET: - type = SOCK_STREAM; - break; - } - - const FileDescriptor another_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, type, 0)); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - if (sockets->first_addr()->sa_data[0] != 0) { - ASSERT_THAT(connect(another_socket.get(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(EPROTOTYPE)); - } else { - ASSERT_THAT(connect(another_socket.get(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(ECONNREFUSED)); - } - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, ConnectNonListening) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -TEST_P(AllSocketPairTest, ConnectToFilePath) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - constexpr char kPath[] = "/tmp"; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - ASSERT_THAT( - connect(sockets->second_fd(), - reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -TEST_P(AllSocketPairTest, ConnectToInvalidAbstractPath) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - constexpr char kPath[] = "\0nonexistent"; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - ASSERT_THAT( - connect(sockets->second_fd(), - reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -TEST_P(AllSocketPairTest, SelfConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, ConnectWithoutListen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -TEST_P(AllSocketPairTest, Accept) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - ASSERT_THAT(close(accepted), SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, AcceptValidAddrLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - struct sockaddr_un addr = {}; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - accepted = accept(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&addr), &addr_len), - SyscallSucceeds()); - ASSERT_THAT(close(accepted), SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, AcceptNegativeAddrLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - // With a negative addr_len, accept returns EINVAL, - struct sockaddr_un addr = {}; - socklen_t addr_len = -1; - ASSERT_THAT(accept(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&addr), &addr_len), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, AcceptLargePositiveAddrLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - // With a large (positive) addr_len, accept does not return EINVAL. - int accepted = -1; - char addr_buf[200]; - socklen_t addr_len = sizeof(addr_buf); - ASSERT_THAT(accepted = accept(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(addr_buf), - &addr_len), - SyscallSucceeds()); - // addr_len should have been updated by accept(). - EXPECT_LT(addr_len, sizeof(addr_buf)); - ASSERT_THAT(close(accepted), SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, AcceptVeryLargePositiveAddrLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - // With a large (positive) addr_len, accept does not return EINVAL. - int accepted = -1; - char addr_buf[2000]; - socklen_t addr_len = sizeof(addr_buf); - ASSERT_THAT(accepted = accept(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(addr_buf), - &addr_len), - SyscallSucceeds()); - // addr_len should have been updated by accept(). - EXPECT_LT(addr_len, sizeof(addr_buf)); - ASSERT_THAT(close(accepted), SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, AcceptWithoutBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, AcceptWithoutListen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, GetRemoteAddress) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - socklen_t addr_len = sockets->first_addr_size(); - struct sockaddr_storage addr = {}; - ASSERT_THAT( - getpeername(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, sockets->first_addr_len()); - EXPECT_EQ(0, memcmp(&addr, sockets->first_addr(), sockets->first_addr_len())); -} - -TEST_P(AllSocketPairTest, UnboundGetLocalAddress) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - socklen_t addr_len = sockets->first_addr_size(); - struct sockaddr_storage addr = {}; - ASSERT_THAT( - getsockname(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, 2); - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -TEST_P(AllSocketPairTest, BoundGetLocalAddress) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - socklen_t addr_len = sockets->first_addr_size(); - struct sockaddr_storage addr = {}; - ASSERT_THAT( - getsockname(sockets->second_fd(), (struct sockaddr*)(&addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, sockets->second_addr_len()); - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -TEST_P(AllSocketPairTest, BoundConnector) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, UnboundSenderAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - FileDescriptor accepted_fd(accepted); - - int i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&addr), &addr_len), - SyscallSucceedsWithValue(sizeof(i))); - EXPECT_EQ(addr_len, 0); -} - -TEST_P(AllSocketPairTest, BoundSenderAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - FileDescriptor accepted_fd(accepted); - - int i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&addr), &addr_len), - SyscallSucceedsWithValue(sizeof(i))); - EXPECT_EQ(addr_len, sockets->second_addr_len()); - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -TEST_P(AllSocketPairTest, BindAfterConnectSenderAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - FileDescriptor accepted_fd(accepted); - - int i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&addr), &addr_len), - SyscallSucceedsWithValue(sizeof(i))); - EXPECT_EQ(addr_len, sockets->second_addr_len()); - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -TEST_P(AllSocketPairTest, BindAfterAcceptSenderAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - FileDescriptor accepted_fd(accepted); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - int i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - RetryEINTR(recvfrom)(accepted_fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&addr), &addr_len), - SyscallSucceedsWithValue(sizeof(i))); - EXPECT_EQ(addr_len, sockets->second_addr_len()); - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, AllSocketPairTest, - ::testing::ValuesIn(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK}))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/accept_bind_stream.cc b/test/syscalls/linux/accept_bind_stream.cc deleted file mode 100644 index 4857f160b..000000000 --- a/test/syscalls/linux/accept_bind_stream.cc +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdio.h> -#include <sys/un.h> - -#include <algorithm> -#include <vector> - -#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 { -namespace testing { - -namespace { - -TEST_P(AllSocketPairTest, BoundSenderAddrCoalesced) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int accepted = -1; - ASSERT_THAT(accepted = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - FileDescriptor closer(accepted); - - int i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->second_addr(), - sockets->second_addr_size()), - SyscallSucceeds()); - - i = 0; - ASSERT_THAT(RetryEINTR(send)(sockets->second_fd(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - - int ri[2] = {0, 0}; - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT( - RetryEINTR(recvfrom)(accepted, ri, sizeof(ri), 0, - reinterpret_cast<sockaddr*>(&addr), &addr_len), - SyscallSucceedsWithValue(sizeof(ri))); - EXPECT_EQ(addr_len, sockets->second_addr_len()); - - EXPECT_EQ( - memcmp(&addr, sockets->second_addr(), - std::min((size_t)addr_len, (size_t)sockets->second_addr_len())), - 0); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, AllSocketPairTest, - ::testing::ValuesIn(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK}))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/access.cc b/test/syscalls/linux/access.cc deleted file mode 100644 index bcc25cef4..000000000 --- a/test/syscalls/linux/access.cc +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::Ge; - -namespace gvisor { -namespace testing { - -namespace { - -class AccessTest : public ::testing::Test { - public: - std::string CreateTempFile(int perm) { - const std::string path = NewTempAbsPath(); - const int fd = open(path.c_str(), O_CREAT | O_RDONLY, perm); - TEST_PCHECK(fd > 0); - TEST_PCHECK(close(fd) == 0); - return path; - } - - protected: - // SetUp creates various configurations of files. - void SetUp() override { - // Move to the temporary directory. This allows us to reason more easily - // about absolute and relative paths. - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - - // Create an empty file, standard permissions. - relfile_ = NewTempRelPath(); - int fd; - ASSERT_THAT(fd = open(relfile_.c_str(), O_CREAT | O_TRUNC, 0644), - SyscallSucceedsWithValue(Ge(0))); - ASSERT_THAT(close(fd), SyscallSucceeds()); - absfile_ = GetAbsoluteTestTmpdir() + "/" + relfile_; - - // Create an empty directory, no writable permissions. - absdir_ = NewTempAbsPath(); - reldir_ = JoinPath(Basename(absdir_), ""); - ASSERT_THAT(mkdir(reldir_.c_str(), 0555), SyscallSucceeds()); - - // This file doesn't exist. - relnone_ = NewTempRelPath(); - absnone_ = GetAbsoluteTestTmpdir() + "/" + relnone_; - } - - // TearDown unlinks created files. - void TearDown() override { - ASSERT_THAT(unlink(absfile_.c_str()), SyscallSucceeds()); - ASSERT_THAT(rmdir(absdir_.c_str()), SyscallSucceeds()); - } - - std::string relfile_; - std::string reldir_; - - std::string absfile_; - std::string absdir_; - - std::string relnone_; - std::string absnone_; -}; - -TEST_F(AccessTest, RelativeFile) { - EXPECT_THAT(access(relfile_.c_str(), R_OK), SyscallSucceeds()); -} - -TEST_F(AccessTest, RelativeDir) { - EXPECT_THAT(access(reldir_.c_str(), R_OK | X_OK), SyscallSucceeds()); -} - -TEST_F(AccessTest, AbsFile) { - EXPECT_THAT(access(absfile_.c_str(), R_OK), SyscallSucceeds()); -} - -TEST_F(AccessTest, AbsDir) { - EXPECT_THAT(access(absdir_.c_str(), R_OK | X_OK), SyscallSucceeds()); -} - -TEST_F(AccessTest, RelDoesNotExist) { - EXPECT_THAT(access(relnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT)); -} - -TEST_F(AccessTest, AbsDoesNotExist) { - EXPECT_THAT(access(absnone_.c_str(), R_OK), SyscallFailsWithErrno(ENOENT)); -} - -TEST_F(AccessTest, InvalidMode) { - EXPECT_THAT(access(relfile_.c_str(), 0xffffffff), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(AccessTest, NoPerms) { - // Drop capabilities that allow us to override permissions. We must drop - // PERMITTED because access() checks those instead of EFFECTIVE. - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE)); - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH)); - - EXPECT_THAT(access(absdir_.c_str(), W_OK), SyscallFailsWithErrno(EACCES)); -} - -TEST_F(AccessTest, InvalidName) { - EXPECT_THAT(access(reinterpret_cast<char*>(0x1234), W_OK), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(AccessTest, UsrReadOnly) { - // Drop capabilities that allow us to override permissions. We must drop - // PERMITTED because access() checks those instead of EFFECTIVE. - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE)); - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH)); - - const std::string filename = CreateTempFile(0400); - EXPECT_THAT(access(filename.c_str(), R_OK), SyscallSucceeds()); - EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -TEST_F(AccessTest, UsrReadExec) { - // Drop capabilities that allow us to override permissions. We must drop - // PERMITTED because access() checks those instead of EFFECTIVE. - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_OVERRIDE)); - ASSERT_NO_ERRNO(DropPermittedCapability(CAP_DAC_READ_SEARCH)); - - const std::string filename = CreateTempFile(0500); - EXPECT_THAT(access(filename.c_str(), R_OK | X_OK), SyscallSucceeds()); - EXPECT_THAT(access(filename.c_str(), W_OK), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -TEST_F(AccessTest, UsrReadWrite) { - const std::string filename = CreateTempFile(0600); - EXPECT_THAT(access(filename.c_str(), R_OK | W_OK), SyscallSucceeds()); - EXPECT_THAT(access(filename.c_str(), X_OK), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -TEST_F(AccessTest, UsrReadWriteExec) { - const std::string filename = CreateTempFile(0700); - EXPECT_THAT(access(filename.c_str(), R_OK | W_OK | X_OK), SyscallSucceeds()); - EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/affinity.cc b/test/syscalls/linux/affinity.cc deleted file mode 100644 index 128364c34..000000000 --- a/test/syscalls/linux/affinity.cc +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sched.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/str_split.h" -#include "test/util/cleanup.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -// These tests are for both the sched_getaffinity(2) and sched_setaffinity(2) -// syscalls. -class AffinityTest : public ::testing::Test { - protected: - void SetUp() override { - EXPECT_THAT( - // Needs use the raw syscall to get the actual size. - cpuset_size_ = syscall(SYS_sched_getaffinity, /*pid=*/0, - sizeof(cpu_set_t), &mask_), - SyscallSucceeds()); - // Lots of tests rely on having more than 1 logical processor available. - EXPECT_GT(CPU_COUNT(&mask_), 1); - } - - static PosixError ClearLowestBit(cpu_set_t* mask, size_t cpus) { - const size_t mask_size = CPU_ALLOC_SIZE(cpus); - for (size_t n = 0; n < cpus; ++n) { - if (CPU_ISSET_S(n, mask_size, mask)) { - CPU_CLR_S(n, mask_size, mask); - return NoError(); - } - } - return PosixError(EINVAL, "No bit to clear, mask is empty"); - } - - PosixError ClearLowestBit() { return ClearLowestBit(&mask_, CPU_SETSIZE); } - - // Stores the initial cpu mask for this process. - cpu_set_t mask_ = {}; - int cpuset_size_ = 0; -}; - -// sched_getaffinity(2) is implemented. -TEST_F(AffinityTest, SchedGetAffinityImplemented) { - EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), - SyscallSucceeds()); -} - -// PID is not found. -TEST_F(AffinityTest, SchedGetAffinityInvalidPID) { - // Flaky, but it's tough to avoid a race condition when finding an unused pid - EXPECT_THAT(sched_getaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), - SyscallFailsWithErrno(ESRCH)); -} - -// PID is not found. -TEST_F(AffinityTest, SchedSetAffinityInvalidPID) { - // Flaky, but it's tough to avoid a race condition when finding an unused pid - EXPECT_THAT(sched_setaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), - SyscallFailsWithErrno(ESRCH)); -} - -TEST_F(AffinityTest, SchedSetAffinityZeroMask) { - CPU_ZERO(&mask_); - EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), - SyscallFailsWithErrno(EINVAL)); -} - -// N.B. This test case relies on cpuset_size_ larger than the actual number of -// of all existing CPUs. Check your machine if the test fails. -TEST_F(AffinityTest, SchedSetAffinityNonexistentCPUDropped) { - cpu_set_t mask = mask_; - // Add a nonexistent CPU. - // - // The number needs to be larger than the possible number of CPU available, - // but smaller than the number of the CPU that the kernel claims to support -- - // it's implicitly returned by raw sched_getaffinity syscall. - CPU_SET(cpuset_size_ * 8 - 1, &mask); - EXPECT_THAT( - // Use raw syscall because it will be rejected by the libc wrapper - // otherwise. - syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask), - SyscallSucceeds()) - << "failed with cpumask : " << CPUSetToString(mask) - << ", cpuset_size_ : " << cpuset_size_; - cpu_set_t newmask; - EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), - SyscallSucceeds()); - EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) - << "got: " << CPUSetToString(newmask) - << " != expected: " << CPUSetToString(mask_); -} - -TEST_F(AffinityTest, SchedSetAffinityOnlyNonexistentCPUFails) { - // Make an empty cpu set. - CPU_ZERO(&mask_); - // Add a nonexistent CPU. - // - // The number needs to be larger than the possible number of CPU available, - // but smaller than the number of the CPU that the kernel claims to support -- - // it's implicitly returned by raw sched_getaffinity syscall. - int cpu = cpuset_size_ * 8 - 1; - if (cpu <= NumCPUs()) { - GTEST_SKIP() << "Skipping test: cpu " << cpu << " exists"; - } - CPU_SET(cpu, &mask_); - EXPECT_THAT( - // Use raw syscall because it will be rejected by the libc wrapper - // otherwise. - syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(AffinityTest, SchedSetAffinityInvalidSize) { - EXPECT_GT(cpuset_size_, 0); - // Not big enough. - EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ - 1, &mask_), - SyscallFailsWithErrno(EINVAL)); - // Not a multiple of word size. - EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ + 1, &mask_), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(AffinityTest, Sanity) { - ASSERT_NO_ERRNO(ClearLowestBit()); - EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), - SyscallSucceeds()); - cpu_set_t newmask; - EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), - SyscallSucceeds()); - EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) - << "got: " << CPUSetToString(newmask) - << " != expected: " << CPUSetToString(mask_); -} - -TEST_F(AffinityTest, NewThread) { - SKIP_IF(CPU_COUNT(&mask_) < 3); - ASSERT_NO_ERRNO(ClearLowestBit()); - ASSERT_NO_ERRNO(ClearLowestBit()); - EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), - SyscallSucceeds()); - ScopedThread([this]() { - cpu_set_t child_mask; - ASSERT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask), - SyscallSucceeds()); - ASSERT_TRUE(CPU_EQUAL(&child_mask, &mask_)) - << "child cpu mask: " << CPUSetToString(child_mask) - << " != parent cpu mask: " << CPUSetToString(mask_); - }); -} - -TEST_F(AffinityTest, ConsistentWithProcCpuInfo) { - // Count how many cpus are shown in /proc/cpuinfo. - std::string cpuinfo = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo")); - int count = 0; - for (auto const& line : absl::StrSplit(cpuinfo, '\n')) { - if (absl::StartsWith(line, "processor")) { - count++; - } - } - EXPECT_GE(count, CPU_COUNT(&mask_)); -} - -TEST_F(AffinityTest, ConsistentWithProcStat) { - // Count how many cpus are shown in /proc/stat. - std::string stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); - int count = 0; - for (auto const& line : absl::StrSplit(stat, '\n')) { - if (absl::StartsWith(line, "cpu") && !absl::StartsWith(line, "cpu ")) { - count++; - } - } - EXPECT_GE(count, CPU_COUNT(&mask_)); -} - -TEST_F(AffinityTest, SmallCpuMask) { - const int num_cpus = NumCPUs(); - const size_t mask_size = CPU_ALLOC_SIZE(num_cpus); - cpu_set_t* mask = CPU_ALLOC(num_cpus); - ASSERT_NE(mask, nullptr); - const auto free_mask = Cleanup([&] { CPU_FREE(mask); }); - - CPU_ZERO_S(mask_size, mask); - ASSERT_THAT(sched_getaffinity(0, mask_size, mask), SyscallSucceeds()); -} - -TEST_F(AffinityTest, LargeCpuMask) { - // Allocate mask bigger than cpu_set_t normally allocates. - const size_t cpus = CPU_SETSIZE * 8; - const size_t mask_size = CPU_ALLOC_SIZE(cpus); - - cpu_set_t* large_mask = CPU_ALLOC(cpus); - auto free_mask = Cleanup([large_mask] { CPU_FREE(large_mask); }); - CPU_ZERO_S(mask_size, large_mask); - - // Check that get affinity with large mask works as expected. - ASSERT_THAT(sched_getaffinity(/*pid=*/0, mask_size, large_mask), - SyscallSucceeds()); - EXPECT_TRUE(CPU_EQUAL(&mask_, large_mask)) - << "got: " << CPUSetToString(*large_mask, cpus) - << " != expected: " << CPUSetToString(mask_); - - // Check that set affinity with large mask works as expected. - ASSERT_NO_ERRNO(ClearLowestBit(large_mask, cpus)); - EXPECT_THAT(sched_setaffinity(/*pid=*/0, mask_size, large_mask), - SyscallSucceeds()); - - cpu_set_t* new_mask = CPU_ALLOC(cpus); - auto free_new_mask = Cleanup([new_mask] { CPU_FREE(new_mask); }); - CPU_ZERO_S(mask_size, new_mask); - EXPECT_THAT(sched_getaffinity(/*pid=*/0, mask_size, new_mask), - SyscallSucceeds()); - - EXPECT_TRUE(CPU_EQUAL_S(mask_size, large_mask, new_mask)) - << "got: " << CPUSetToString(*new_mask, cpus) - << " != expected: " << CPUSetToString(*large_mask, cpus); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/aio.cc b/test/syscalls/linux/aio.cc deleted file mode 100644 index 806d5729e..000000000 --- a/test/syscalls/linux/aio.cc +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/aio_abi.h> -#include <sys/mman.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <string> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::_; - -namespace gvisor { -namespace testing { -namespace { - -// Returns the size of the VMA containing the given address. -PosixErrorOr<size_t> VmaSizeAt(uintptr_t addr) { - ASSIGN_OR_RETURN_ERRNO(std::string proc_self_maps, - GetContents("/proc/self/maps")); - ASSIGN_OR_RETURN_ERRNO(auto entries, ParseProcMaps(proc_self_maps)); - // Use binary search to find the first VMA that might contain addr. - ProcMapsEntry target = {}; - target.end = addr; - auto it = - std::upper_bound(entries.begin(), entries.end(), target, - [](const ProcMapsEntry& x, const ProcMapsEntry& y) { - return x.end < y.end; - }); - // Check that it actually contains addr. - if (it == entries.end() || addr < it->start) { - return PosixError(ENOENT, absl::StrCat("no VMA contains address ", addr)); - } - return it->end - it->start; -} - -constexpr char kData[] = "hello world!"; - -int SubmitCtx(aio_context_t ctx, long nr, struct iocb** iocbpp) { - return syscall(__NR_io_submit, ctx, nr, iocbpp); -} - -class AIOTest : public FileTest { - public: - AIOTest() : ctx_(0) {} - - int SetupContext(unsigned int nr) { - return syscall(__NR_io_setup, nr, &ctx_); - } - - int Submit(long nr, struct iocb** iocbpp) { - return SubmitCtx(ctx_, nr, iocbpp); - } - - int GetEvents(long min, long max, struct io_event* events, - struct timespec* timeout) { - return RetryEINTR(syscall)(__NR_io_getevents, ctx_, min, max, events, - timeout); - } - - int DestroyContext() { return syscall(__NR_io_destroy, ctx_); } - - void TearDown() override { - FileTest::TearDown(); - if (ctx_ != 0) { - ASSERT_THAT(DestroyContext(), SyscallSucceeds()); - ctx_ = 0; - } - } - - struct iocb CreateCallback() { - struct iocb cb = {}; - cb.aio_data = 0x123; - cb.aio_fildes = test_file_fd_.get(); - cb.aio_lio_opcode = IOCB_CMD_PWRITE; - cb.aio_buf = reinterpret_cast<uint64_t>(kData); - cb.aio_offset = 0; - cb.aio_nbytes = strlen(kData); - return cb; - } - - protected: - aio_context_t ctx_; -}; - -TEST_F(AIOTest, BasicWrite) { - // Copied from fs/aio.c. - constexpr unsigned AIO_RING_MAGIC = 0xa10a10a1; - struct aio_ring { - unsigned id; - unsigned nr; - unsigned head; - unsigned tail; - unsigned magic; - unsigned compat_features; - unsigned incompat_features; - unsigned header_length; - struct io_event io_events[0]; - }; - - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - // Check that 'ctx_' points to a valid address. libaio uses it to check if - // aio implementation uses aio_ring. gVisor doesn't and returns all zeroes. - // Linux implements aio_ring, so skip the zeroes check. - // - // TODO(gvisor.dev/issue/204): Remove when gVisor implements aio_ring. - auto ring = reinterpret_cast<struct aio_ring*>(ctx_); - auto magic = IsRunningOnGvisor() ? 0 : AIO_RING_MAGIC; - EXPECT_EQ(ring->magic, magic); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - // Submit the request. - ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1)); - - // Get the reply. - struct io_event events[1]; - ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1)); - - // Verify that it is as expected. - EXPECT_EQ(events[0].data, 0x123); - EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb)); - EXPECT_EQ(events[0].res, strlen(kData)); - - // Verify that the file contains the contents. - char verify_buf[sizeof(kData)] = {}; - ASSERT_THAT(read(test_file_fd_.get(), verify_buf, sizeof(kData)), - SyscallSucceedsWithValue(strlen(kData))); - EXPECT_STREQ(verify_buf, kData); -} - -TEST_F(AIOTest, BadWrite) { - // Create a pipe and immediately close the read end. - int pipefd[2]; - ASSERT_THAT(pipe(pipefd), SyscallSucceeds()); - - FileDescriptor rfd(pipefd[0]); - FileDescriptor wfd(pipefd[1]); - - rfd.reset(); // Close the read end. - - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - struct iocb cb = CreateCallback(); - // Try to write to the read end. - cb.aio_fildes = wfd.get(); - struct iocb* cbs[1] = {&cb}; - - // Submit the request. - ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1)); - - // Get the reply. - struct io_event events[1]; - ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1)); - - // Verify that it fails with the right error code. - EXPECT_EQ(events[0].data, 0x123); - EXPECT_EQ(events[0].obj, reinterpret_cast<uint64_t>(&cb)); - EXPECT_LT(events[0].res, 0); -} - -TEST_F(AIOTest, ExitWithPendingIo) { - // Setup a context that is 100 entries deep. - ASSERT_THAT(SetupContext(100), SyscallSucceeds()); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[] = {&cb}; - - // Submit a request but don't complete it to make it pending. - for (int i = 0; i < 100; ++i) { - EXPECT_THAT(Submit(1, cbs), SyscallSucceeds()); - } - - ASSERT_THAT(DestroyContext(), SyscallSucceeds()); - ctx_ = 0; -} - -int Submitter(void* arg) { - auto test = reinterpret_cast<AIOTest*>(arg); - - struct iocb cb = test->CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - // Submit the request. - TEST_CHECK(test->Submit(1, cbs) == 1); - return 0; -} - -TEST_F(AIOTest, CloneVm) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - const size_t kStackSize = 5 * kPageSize; - std::unique_ptr<char[]> stack(new char[kStackSize]); - char* bp = stack.get() + kStackSize; - pid_t child; - ASSERT_THAT(child = clone(Submitter, bp, CLONE_VM | SIGCHLD, - reinterpret_cast<void*>(this)), - SyscallSucceeds()); - - // Get the reply. - struct io_event events[1]; - ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1)); - - // Verify that it is as expected. - EXPECT_EQ(events[0].data, 0x123); - EXPECT_EQ(events[0].res, strlen(kData)); - - // Verify that the file contains the contents. - char verify_buf[32] = {}; - ASSERT_THAT(read(test_file_fd_.get(), &verify_buf[0], strlen(kData)), - SyscallSucceeds()); - EXPECT_EQ(strcmp(kData, &verify_buf[0]), 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -// Tests that AIO context can be remapped to a different address. -TEST_F(AIOTest, Mremap) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - const size_t ctx_size = - ASSERT_NO_ERRNO_AND_VALUE(VmaSizeAt(reinterpret_cast<uintptr_t>(ctx_))); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - // Reserve address space for the mremap target so we have something safe to - // map over. - Mapping dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(ctx_size, PROT_READ, MAP_PRIVATE)); - - // Remap context 'handle' to a different address. - ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(), - MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()), - IsPosixErrorOkAndHolds(dst.ptr())); - aio_context_t old_ctx = ctx_; - ctx_ = reinterpret_cast<aio_context_t>(dst.addr()); - // io_destroy() will unmap dst now. - dst.release(); - - // Check that submitting the request with the old 'ctx_' fails. - ASSERT_THAT(SubmitCtx(old_ctx, 1, cbs), SyscallFailsWithErrno(EINVAL)); - - // Submit the request with the new 'ctx_'. - ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1)); - - // Remap again. - dst = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(ctx_size, PROT_READ, MAP_PRIVATE)); - ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(), - MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()), - IsPosixErrorOkAndHolds(dst.ptr())); - ctx_ = reinterpret_cast<aio_context_t>(dst.addr()); - dst.release(); - - // Get the reply with yet another 'ctx_' and verify it. - struct io_event events[1]; - ASSERT_THAT(GetEvents(1, 1, events, nullptr), SyscallSucceedsWithValue(1)); - EXPECT_EQ(events[0].data, 0x123); - EXPECT_EQ(events[0].obj, reinterpret_cast<long>(&cb)); - EXPECT_EQ(events[0].res, strlen(kData)); - - // Verify that the file contains the contents. - char verify_buf[sizeof(kData)] = {}; - ASSERT_THAT(read(test_file_fd_.get(), verify_buf, sizeof(kData)), - SyscallSucceedsWithValue(strlen(kData))); - EXPECT_STREQ(verify_buf, kData); -} - -// Tests that AIO context cannot be expanded with mremap. -TEST_F(AIOTest, MremapExpansion) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - const size_t ctx_size = - ASSERT_NO_ERRNO_AND_VALUE(VmaSizeAt(reinterpret_cast<uintptr_t>(ctx_))); - - // Reserve address space for the mremap target so we have something safe to - // map over. - Mapping dst = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(ctx_size + kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Test that remapping to a larger address range fails. - ASSERT_THAT(Mremap(reinterpret_cast<void*>(ctx_), ctx_size, dst.len(), - MREMAP_FIXED | MREMAP_MAYMOVE, dst.ptr()), - PosixErrorIs(EFAULT, _)); - - // mm/mremap.c:sys_mremap() => mremap_to() does do_munmap() of the destination - // before it hits the VM_DONTEXPAND check in vma_to_resize(), so we should no - // longer munmap it (another thread may have created a mapping there). - dst.release(); -} - -// Tests that AIO calls fail if context's address is inaccessible. -TEST_F(AIOTest, Mprotect) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - ASSERT_THAT(Submit(1, cbs), SyscallSucceedsWithValue(1)); - - // Makes the context 'handle' inaccessible and check that all subsequent - // calls fail. - ASSERT_THAT(mprotect(reinterpret_cast<void*>(ctx_), kPageSize, PROT_NONE), - SyscallSucceeds()); - struct io_event events[1]; - EXPECT_THAT(GetEvents(1, 1, events, nullptr), SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(DestroyContext(), SyscallFailsWithErrno(EINVAL)); - - // Prevent TearDown from attempting to destroy the context and fail. - ctx_ = 0; -} - -TEST_F(AIOTest, Timeout) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - struct timespec timeout; - timeout.tv_sec = 0; - timeout.tv_nsec = 10; - struct io_event events[1]; - ASSERT_THAT(GetEvents(1, 1, events, &timeout), SyscallSucceedsWithValue(0)); -} - -class AIOReadWriteParamTest : public AIOTest, - public ::testing::WithParamInterface<int> {}; - -TEST_P(AIOReadWriteParamTest, BadOffset) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - // Create a buffer that we can write to. - char buf[] = "hello world!"; - cb.aio_buf = reinterpret_cast<uint64_t>(buf); - - // Set the operation on the callback and give a negative offset. - const int opcode = GetParam(); - cb.aio_lio_opcode = opcode; - - iovec iov = {}; - if (opcode == IOCB_CMD_PREADV || opcode == IOCB_CMD_PWRITEV) { - // Create a valid iovec and set it in the callback. - iov.iov_base = reinterpret_cast<void*>(buf); - iov.iov_len = 1; - cb.aio_buf = reinterpret_cast<uint64_t>(&iov); - // aio_nbytes is the number of iovecs. - cb.aio_nbytes = 1; - } - - // Pass a negative offset. - cb.aio_offset = -1; - - // Should get error on submission. - ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EINVAL)); -} - -INSTANTIATE_TEST_SUITE_P(BadOffset, AIOReadWriteParamTest, - ::testing::Values(IOCB_CMD_PREAD, IOCB_CMD_PWRITE, - IOCB_CMD_PREADV, IOCB_CMD_PWRITEV)); - -class AIOVectorizedParamTest : public AIOTest, - public ::testing::WithParamInterface<int> {}; - -TEST_P(AIOVectorizedParamTest, BadIOVecs) { - // Setup a context that is 128 entries deep. - ASSERT_THAT(SetupContext(128), SyscallSucceeds()); - - struct iocb cb = CreateCallback(); - struct iocb* cbs[1] = {&cb}; - - // Modify the callback to use the operation from the param. - cb.aio_lio_opcode = GetParam(); - - // Create an iovec with address in kernel range, and pass that as the buffer. - iovec iov = {}; - iov.iov_base = reinterpret_cast<void*>(0xFFFFFFFF00000000); - iov.iov_len = 1; - cb.aio_buf = reinterpret_cast<uint64_t>(&iov); - // aio_nbytes is the number of iovecs. - cb.aio_nbytes = 1; - - // Should get error on submission. - ASSERT_THAT(Submit(1, cbs), SyscallFailsWithErrno(EFAULT)); -} - -INSTANTIATE_TEST_SUITE_P(BadIOVecs, AIOVectorizedParamTest, - ::testing::Values(IOCB_CMD_PREADV, IOCB_CMD_PWRITEV)); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/alarm.cc b/test/syscalls/linux/alarm.cc deleted file mode 100644 index 940c97285..000000000 --- a/test/syscalls/linux/alarm.cc +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// N.B. Below, main blocks SIGALRM. Test cases must unblock it if they want -// delivery. - -void do_nothing_handler(int sig, siginfo_t* siginfo, void* arg) {} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and read. -TEST(AlarmTest, Interrupt_NoRandomSave) { - int pipe_fds[2]; - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - FileDescriptor read_fd(pipe_fds[0]); - FileDescriptor write_fd(pipe_fds[1]); - - // Use a signal handler that interrupts but does nothing rather than using the - // default terminate action. - struct sigaction sa; - sa.sa_sigaction = do_nothing_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = 0; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Actually allow SIGALRM delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - // Alarm in 20 second, which should be well after read blocks below. - ASSERT_THAT(alarm(20), SyscallSucceeds()); - - char buf; - ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallFailsWithErrno(EINTR)); -} - -/* Count of the number of SIGALARMS handled. */ -static volatile int alarms_received = 0; - -void inc_alarms_handler(int sig, siginfo_t* siginfo, void* arg) { - alarms_received++; -} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and read. -TEST(AlarmTest, Restart_NoRandomSave) { - alarms_received = 0; - - int pipe_fds[2]; - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - FileDescriptor read_fd(pipe_fds[0]); - // Write end closed by thread below. - - struct sigaction sa; - sa.sa_sigaction = inc_alarms_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Spawn a thread to eventually unblock the read below. - ScopedThread t([pipe_fds] { - absl::SleepFor(absl::Seconds(30)); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); - }); - - // Actually allow SIGALRM delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - // Alarm in 20 second, which should be well after read blocks below, but - // before it returns. - ASSERT_THAT(alarm(20), SyscallSucceeds()); - - // Read and eventually get an EOF from the writer closing. If SA_RESTART - // didn't work, then the alarm would not have fired and we wouldn't increment - // our alarms_received count in our signal handler, or we would have not - // restarted the syscall gracefully, which we expect below in order to be - // able to get the final EOF on the pipe. - char buf; - ASSERT_THAT(read(read_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_EQ(alarms_received, 1); - - t.Join(); -} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and pause. -TEST(AlarmTest, SaSiginfo_NoRandomSave) { - // Use a signal handler that interrupts but does nothing rather than using the - // default terminate action. - struct sigaction sa; - sa.sa_sigaction = do_nothing_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Actually allow SIGALRM delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - // Alarm in 20 second, which should be well after pause blocks below. - ASSERT_THAT(alarm(20), SyscallSucceeds()); - ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR)); -} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and pause. -TEST(AlarmTest, SaInterrupt_NoRandomSave) { - // Use a signal handler that interrupts but does nothing rather than using the - // default terminate action. - struct sigaction sa; - sa.sa_sigaction = do_nothing_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_INTERRUPT; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Actually allow SIGALRM delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - // Alarm in 20 second, which should be well after pause blocks below. - ASSERT_THAT(alarm(20), SyscallSucceeds()); - ASSERT_THAT(pause(), SyscallFailsWithErrno(EINTR)); -} - -TEST(AlarmTest, UserModeSpinning) { - alarms_received = 0; - - struct sigaction sa = {}; - sa.sa_sigaction = inc_alarms_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Actually allow SIGALRM delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - // Alarm in 20 second, which should be well into the loop below. - ASSERT_THAT(alarm(20), SyscallSucceeds()); - // Make sure that the signal gets delivered even if we are spinning in user - // mode when it arrives. - while (!alarms_received) { - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // These tests depend on delivering SIGALRM to the main thread. Block SIGALRM - // so that any other threads created by TestInit will also have SIGALRM - // blocked. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGALRM); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/arch_prctl.cc b/test/syscalls/linux/arch_prctl.cc deleted file mode 100644 index 81bf5a775..000000000 --- a/test/syscalls/linux/arch_prctl.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <asm/prctl.h> -#include <sys/prctl.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -// glibc does not provide a prototype for arch_prctl() so declare it here. -extern "C" int arch_prctl(int code, uintptr_t addr); - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ArchPrctlTest, GetSetFS) { - uintptr_t orig; - const uintptr_t kNonCanonicalFsbase = 0x4141414142424242; - - // Get the original FS.base and then set it to the same value (this is - // intentional because FS.base is the TLS pointer so we cannot change it - // arbitrarily). - ASSERT_THAT(arch_prctl(ARCH_GET_FS, reinterpret_cast<uintptr_t>(&orig)), - SyscallSucceeds()); - ASSERT_THAT(arch_prctl(ARCH_SET_FS, orig), SyscallSucceeds()); - - // Trying to set FS.base to a non-canonical value should return an error. - ASSERT_THAT(arch_prctl(ARCH_SET_FS, kNonCanonicalFsbase), - SyscallFailsWithErrno(EPERM)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/bad.cc b/test/syscalls/linux/bad.cc deleted file mode 100644 index a26fc6af3..000000000 --- a/test/syscalls/linux/bad.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/syscall.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { -#ifdef __x86_64__ -// get_kernel_syms is not supported in Linux > 2.6, and not implemented in -// gVisor. -constexpr uint32_t kNotImplementedSyscall = SYS_get_kernel_syms; -#elif __aarch64__ -// Use the last of arch_specific_syscalls which are not implemented on arm64. -constexpr uint32_t kNotImplementedSyscall = __NR_arch_specific_syscall + 15; -#endif - -TEST(BadSyscallTest, NotImplemented) { - EXPECT_THAT(syscall(kNotImplementedSyscall), SyscallFailsWithErrno(ENOSYS)); -} - -TEST(BadSyscallTest, NegativeOne) { - EXPECT_THAT(syscall(-1), SyscallFailsWithErrno(ENOSYS)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/base_poll_test.cc b/test/syscalls/linux/base_poll_test.cc deleted file mode 100644 index ab7a19dd0..000000000 --- a/test/syscalls/linux/base_poll_test.cc +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/base_poll_test.h" - -#include <sys/syscall.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -static volatile int timer_fired = 0; -static void SigAlarmHandler(int, siginfo_t*, void*) { timer_fired = 1; } - -BasePollTest::BasePollTest() { - // Register our SIGALRM handler, but save the original so we can restore in - // the destructor. - struct sigaction sa = {}; - sa.sa_sigaction = SigAlarmHandler; - sigfillset(&sa.sa_mask); - TEST_PCHECK(sigaction(SIGALRM, &sa, &original_alarm_sa_) == 0); -} - -BasePollTest::~BasePollTest() { - ClearTimer(); - TEST_PCHECK(sigaction(SIGALRM, &original_alarm_sa_, nullptr) == 0); -} - -void BasePollTest::SetTimer(absl::Duration duration) { - pid_t tgid = getpid(); - pid_t tid = gettid(); - ClearTimer(); - - // Create a new timer thread. - timer_ = absl::make_unique<TimerThread>(absl::Now() + duration, tgid, tid); -} - -bool BasePollTest::TimerFired() const { return timer_fired; } - -void BasePollTest::ClearTimer() { - timer_.reset(); - timer_fired = 0; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/base_poll_test.h b/test/syscalls/linux/base_poll_test.h deleted file mode 100644 index 0d4a6701e..000000000 --- a/test/syscalls/linux/base_poll_test.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_ -#define GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_ - -#include <signal.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <syscall.h> -#include <time.h> -#include <unistd.h> - -#include <memory> - -#include "gtest/gtest.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/time.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -// TimerThread is a cancelable timer. -class TimerThread { - public: - TimerThread(absl::Time deadline, pid_t tgid, pid_t tid) - : thread_([=] { - mu_.Lock(); - mu_.AwaitWithDeadline(absl::Condition(&cancel_), deadline); - if (!cancel_) { - TEST_PCHECK(tgkill(tgid, tid, SIGALRM) == 0); - } - mu_.Unlock(); - }) {} - - ~TimerThread() { Cancel(); } - - void Cancel() { - absl::MutexLock ml(&mu_); - cancel_ = true; - } - - private: - mutable absl::Mutex mu_; - bool cancel_ ABSL_GUARDED_BY(mu_) = false; - - // Must be last to ensure that the destructor for the thread is run before - // any other member of the object is destroyed. - ScopedThread thread_; -}; - -// Base test fixture for poll, select, ppoll, and pselect tests. -// -// This fixture makes use of SIGALRM. The handler is saved in SetUp() and -// restored in TearDown(). -class BasePollTest : public ::testing::Test { - protected: - BasePollTest(); - ~BasePollTest() override; - - // Sets a timer that will send a signal to the calling thread after - // `duration`. - void SetTimer(absl::Duration duration); - - // Returns true if the timer has fired. - bool TimerFired() const; - - // Stops the pending timer (if any) and clear the "fired" state. - void ClearTimer(); - - private: - // Thread that implements the timer. If the timer is stopped, timer_ is null. - // - // We have to use a thread for this purpose because tests using this fixture - // expect to be interrupted by the timer signal, but itimers/alarm(2) send - // thread-group-directed signals, which may be handled by any thread in the - // test process. - std::unique_ptr<TimerThread> timer_; - - // The original SIGALRM handler, to restore in destructor. - struct sigaction original_alarm_sa_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_BASE_POLL_TEST_H_ diff --git a/test/syscalls/linux/bind.cc b/test/syscalls/linux/bind.cc deleted file mode 100644 index 9547c4ab2..000000000 --- a/test/syscalls/linux/bind.cc +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdio.h> -#include <sys/socket.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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST_P(AllSocketPairTest, Bind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(AllSocketPairTest, BindTooLong) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - // first_addr is a sockaddr_storage being used as a sockaddr_un. Use the full - // length which is longer than expected for a Unix socket. - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sizeof(sockaddr_storage)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, DoubleBindSocket) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - EXPECT_THAT( - bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - // Linux 4.09 returns EINVAL here, but some time before 4.19 it switched - // to EADDRINUSE. - AnyOf(SyscallFailsWithErrno(EADDRINUSE), SyscallFailsWithErrno(EINVAL))); -} - -TEST_P(AllSocketPairTest, GetLocalAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - socklen_t addressLength = sockets->first_addr_size(); - struct sockaddr_storage address = {}; - ASSERT_THAT(getsockname(sockets->first_fd(), (struct sockaddr*)(&address), - &addressLength), - SyscallSucceeds()); - EXPECT_EQ( - 0, memcmp(&address, sockets->first_addr(), sockets->first_addr_size())); -} - -TEST_P(AllSocketPairTest, GetLocalAddrWithoutBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - socklen_t addressLength = sockets->first_addr_size(); - struct sockaddr_storage received_address = {}; - ASSERT_THAT( - getsockname(sockets->first_fd(), (struct sockaddr*)(&received_address), - &addressLength), - SyscallSucceeds()); - struct sockaddr_storage want_address = {}; - want_address.ss_family = sockets->first_addr()->sa_family; - EXPECT_EQ(0, memcmp(&received_address, &want_address, addressLength)); -} - -TEST_P(AllSocketPairTest, GetRemoteAddressWithoutConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - socklen_t addressLength = sockets->first_addr_size(); - struct sockaddr_storage address = {}; - ASSERT_THAT(getpeername(sockets->second_fd(), (struct sockaddr*)(&address), - &addressLength), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(AllSocketPairTest, DoubleBindAddress) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - EXPECT_THAT(bind(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(AllSocketPairTest, Unbind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - - // Filesystem Unix sockets do not release their address when closed. - if (sockets->first_addr()->sa_data[0] != 0) { - ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallFailsWithErrno(EADDRINUSE)); - return; - } - - ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, AllSocketPairTest, - ::testing::ValuesIn(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, - SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, - SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK}))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/brk.cc b/test/syscalls/linux/brk.cc deleted file mode 100644 index a03a44465..000000000 --- a/test/syscalls/linux/brk.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdint.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST(BrkTest, BrkSyscallReturnsOldBrkOnFailure) { - auto old_brk = sbrk(0); - EXPECT_THAT(syscall(SYS_brk, reinterpret_cast<void*>(-1)), - SyscallSucceedsWithValue(reinterpret_cast<uintptr_t>(old_brk))); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/chdir.cc b/test/syscalls/linux/chdir.cc deleted file mode 100644 index 3182c228b..000000000 --- a/test/syscalls/linux/chdir.cc +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/limits.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ChdirTest, Success) { - auto old_dir = GetAbsoluteTestTmpdir(); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chdir(temp_dir.path().c_str()), SyscallSucceeds()); - // Temp path destructor deletes the newly created tmp dir and Sentry rejects - // saving when its current dir is still pointing to the path. Switch to a - // permanent path here. - EXPECT_THAT(chdir(old_dir.c_str()), SyscallSucceeds()); -} - -TEST(ChdirTest, PermissionDenied) { - // Drop capabilities that allow us to override directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */)); - EXPECT_THAT(chdir(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChdirTest, NotDir) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - EXPECT_THAT(chdir(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(ChdirTest, NotExist) { - EXPECT_THAT(chdir("/foo/bar"), SyscallFailsWithErrno(ENOENT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/chmod.cc b/test/syscalls/linux/chmod.cc deleted file mode 100644 index 8233df0f8..000000000 --- a/test/syscalls/linux/chmod.cc +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ChmodTest, ChmodFileSucceeds) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - ASSERT_THAT(chmod(file.path().c_str(), 0466), SyscallSucceeds()); - EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, ChmodDirSucceeds) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string fileInDir = NewTempAbsPathInDir(dir.path()); - - ASSERT_THAT(chmod(dir.path().c_str(), 0466), SyscallSucceeds()); - EXPECT_THAT(open(fileInDir.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodFileSucceeds_NoRandomSave) { - // Drop capabilities that allow us to file directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666)); - int fd; - ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds()); - - { - const DisableSave ds; // File permissions are reduced. - ASSERT_THAT(fchmod(fd, 0444), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - } - - EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodDirSucceeds_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - int fd; - ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - - { - const DisableSave ds; // File permissions are reduced. - ASSERT_THAT(fchmod(fd, 0), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - } - - EXPECT_THAT(open(dir.path().c_str(), O_RDONLY), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodBadF) { - ASSERT_THAT(fchmod(-1, 0444), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChmodTest, FchmodatBadF) { - ASSERT_THAT(fchmodat(-1, "foo", 0444, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChmodTest, FchmodFileWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChmodTest, FchmodDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); - - ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChmodTest, FchmodatWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - const auto parent_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(GetAbsoluteTestTmpdir().c_str(), O_PATH | O_DIRECTORY)); - - ASSERT_THAT( - fchmodat(parent_fd.get(), std::string(Basename(temp_file.path())).c_str(), - 0444, 0), - SyscallSucceeds()); - - EXPECT_THAT(open(temp_file.path().c_str(), O_RDWR), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodatNotDir) { - ASSERT_THAT(fchmodat(-1, "", 0444, 0), SyscallFailsWithErrno(ENOENT)); -} - -TEST(ChmodTest, FchmodatFileAbsolutePath) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - ASSERT_THAT(fchmodat(-1, file.path().c_str(), 0444, 0), SyscallSucceeds()); - EXPECT_THAT(open(file.path().c_str(), O_RDWR), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodatDirAbsolutePath) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - int fd; - ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - ASSERT_THAT(fchmodat(-1, dir.path().c_str(), 0, 0), SyscallSucceeds()); - EXPECT_THAT(open(dir.path().c_str(), O_RDONLY), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodatFile) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - int parent_fd; - ASSERT_THAT( - parent_fd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - - ASSERT_THAT( - fchmodat(parent_fd, std::string(Basename(temp_file.path())).c_str(), 0444, - 0), - SyscallSucceeds()); - EXPECT_THAT(close(parent_fd), SyscallSucceeds()); - - EXPECT_THAT(open(temp_file.path().c_str(), O_RDWR), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodatDir) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - int parent_fd; - ASSERT_THAT( - parent_fd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - - int fd; - ASSERT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - ASSERT_THAT( - fchmodat(parent_fd, std::string(Basename(dir.path())).c_str(), 0, 0), - SyscallSucceeds()); - EXPECT_THAT(close(parent_fd), SyscallSucceeds()); - - EXPECT_THAT(open(dir.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, ChmodDowngradeWritability_NoRandomSave) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666)); - - int fd; - ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds()); - - const DisableSave ds; // Permissions are dropped. - ASSERT_THAT(chmod(file.path().c_str(), 0444), SyscallSucceeds()); - EXPECT_THAT(write(fd, "hello", 5), SyscallSucceedsWithValue(5)); - - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(ChmodTest, ChmodFileToNoPermissionsSucceeds) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666)); - - ASSERT_THAT(chmod(file.path().c_str(), 0), SyscallSucceeds()); - - EXPECT_THAT(open(file.path().c_str(), O_RDONLY), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ChmodTest, FchmodDowngradeWritability_NoRandomSave) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - int fd; - ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - - const DisableSave ds; // Permissions are dropped. - ASSERT_THAT(fchmod(fd, 0444), SyscallSucceeds()); - EXPECT_THAT(write(fd, "hello", 5), SyscallSucceedsWithValue(5)); - - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(ChmodTest, FchmodFileToNoPermissionsSucceeds_NoRandomSave) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666)); - - int fd; - ASSERT_THAT(fd = open(file.path().c_str(), O_RDWR), SyscallSucceeds()); - - { - const DisableSave ds; // Permissions are dropped. - ASSERT_THAT(fchmod(fd, 0), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - } - - EXPECT_THAT(open(file.path().c_str(), O_RDONLY), - SyscallFailsWithErrno(EACCES)); -} - -// Verify that we can get a RW FD after chmod, even if a RO fd is left open. -TEST(ChmodTest, ChmodWritableWithOpenFD) { - // FIXME(b/72455313): broken on hostfs. - if (IsRunningOnGvisor()) { - return; - } - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0444)); - - FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - ASSERT_THAT(fchmod(fd1.get(), 0644), SyscallSucceeds()); - - // This FD is writable, even though fd1 has a read-only reference to the file. - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - // fd1 is not writable, but fd2 is. - char c = 'a'; - EXPECT_THAT(WriteFd(fd1.get(), &c, 1), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(WriteFd(fd2.get(), &c, 1), SyscallSucceedsWithValue(1)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc deleted file mode 100644 index ff0d39343..000000000 --- a/test/syscalls/linux/chown.cc +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <grp.h> -#include <sys/types.h> -#include <unistd.h> - -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/synchronization/notification.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(int32_t, scratch_uid1, 65534, "first scratch UID"); -ABSL_FLAG(int32_t, scratch_uid2, 65533, "second scratch UID"); -ABSL_FLAG(int32_t, scratch_gid, 65534, "first scratch GID"); - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ChownTest, FchownBadF) { - ASSERT_THAT(fchown(-1, 0, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChownTest, FchownatBadF) { - ASSERT_THAT(fchownat(-1, "fff", 0, 0, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(ChownTest, FchownFileWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), - SyscallFailsWithErrno(EBADF)); -} - -TEST(ChownTest, FchownDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); - - ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), - SyscallFailsWithErrno(EBADF)); -} - -TEST(ChownTest, FchownatWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - const auto dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); - ASSERT_THAT( - fchownat(dirfd.get(), file.path().c_str(), geteuid(), getegid(), 0), - SyscallSucceeds()); -} - -TEST(ChownTest, FchownatEmptyPath) { - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_RDONLY)); - ASSERT_THAT(fchownat(fd.get(), "", 0, 0, 0), SyscallFailsWithErrno(ENOENT)); -} - -using Chown = - std::function<PosixError(const std::string&, uid_t owner, gid_t group)>; - -class ChownParamTest : public ::testing::TestWithParam<Chown> {}; - -TEST_P(ChownParamTest, ChownFileSucceeds) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_CHOWN))) { - ASSERT_NO_ERRNO(SetCapability(CAP_CHOWN, false)); - } - - const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // At least *try* setting to a group other than the EGID. - gid_t gid; - EXPECT_THAT(gid = getegid(), SyscallSucceeds()); - int num_groups; - EXPECT_THAT(num_groups = getgroups(0, nullptr), SyscallSucceeds()); - if (num_groups > 0) { - std::vector<gid_t> list(num_groups); - EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds()); - // Scan the list of groups for a valid gid. Note that if a group is not - // defined in this local user namespace, then we will see 65534, and the - // group will not chown below as expected. So only change if we find a - // valid group in this list. - for (const gid_t other_gid : list) { - if (other_gid != 65534) { - gid = other_gid; - break; - } - } - } - - EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid)); - - struct stat s = {}; - ASSERT_THAT(stat(file.path().c_str(), &s), SyscallSucceeds()); - EXPECT_EQ(s.st_uid, geteuid()); - EXPECT_EQ(s.st_gid, gid); -} - -TEST_P(ChownParamTest, ChownFilePermissionDenied) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777)); - EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); - - // Drop privileges and change IDs only in child thread, or else this parent - // thread won't be able to open some log files after the test ends. - ScopedThread([&] { - // Drop privileges. - if (HaveCapability(CAP_CHOWN).ValueOrDie()) { - EXPECT_NO_ERRNO(SetCapability(CAP_CHOWN, false)); - } - - // Change EUID and EGID. - // - // See note about POSIX below. - EXPECT_THAT( - syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), - SyscallSucceeds()); - EXPECT_THAT( - syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid1), -1), - SyscallSucceeds()); - - EXPECT_THAT(GetParam()(file.path(), geteuid(), getegid()), - PosixErrorIs(EPERM, ::testing::ContainsRegex("chown"))); - }); -} - -TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_CHOWN)))); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID)))); - - const std::string filename = NewTempAbsPath(); - EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); - - absl::Notification fileCreated, fileChowned; - // Change UID only in child thread, or else this parent thread won't be able - // to open some log files after the test ends. - ScopedThread t([&] { - // POSIX requires that all threads in a process share the same UIDs, so - // the NPTL setresuid wrappers use signals to make all threads execute the - // setresuid syscall. However, we want this thread to have its own set of - // credentials different from the parent process, so we use the raw - // syscall. - EXPECT_THAT( - syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid2), -1), - SyscallSucceeds()); - - // Create file and immediately close it. - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0644)); - fd.reset(); // Close the fd. - - fileCreated.Notify(); - fileChowned.WaitForNotification(); - - EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EACCES)); - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDONLY)); - }); - - fileCreated.WaitForNotification(); - - // Set file's owners to someone different. - EXPECT_NO_ERRNO(GetParam()(filename, absl::GetFlag(FLAGS_scratch_uid1), - absl::GetFlag(FLAGS_scratch_gid))); - - struct stat s; - EXPECT_THAT(stat(filename.c_str(), &s), SyscallSucceeds()); - EXPECT_EQ(s.st_uid, absl::GetFlag(FLAGS_scratch_uid1)); - EXPECT_EQ(s.st_gid, absl::GetFlag(FLAGS_scratch_gid)); - - fileChowned.Notify(); -} - -PosixError errorFromReturn(const std::string& name, int ret) { - if (ret == -1) { - return PosixError(errno, absl::StrCat(name, " failed")); - } - return NoError(); -} - -INSTANTIATE_TEST_SUITE_P( - ChownKinds, ChownParamTest, - ::testing::Values( - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - int rc = chown(path.c_str(), owner, group); - MaybeSave(); - return errorFromReturn("chown", rc); - }, - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - int rc = lchown(path.c_str(), owner, group); - MaybeSave(); - return errorFromReturn("lchown", rc); - }, - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR)); - int rc = fchown(fd.get(), owner, group); - MaybeSave(); - return errorFromReturn("fchown", rc); - }, - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR)); - int rc = fchownat(fd.get(), "", owner, group, AT_EMPTY_PATH); - MaybeSave(); - return errorFromReturn("fchownat-fd", rc); - }, - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)), - O_DIRECTORY | O_RDONLY)); - int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(), - owner, group, 0); - MaybeSave(); - return errorFromReturn("fchownat-dirfd", rc); - }, - [](const std::string& path, uid_t owner, gid_t group) -> PosixError { - ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)), - O_DIRECTORY | O_PATH)); - int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(), - owner, group, 0); - MaybeSave(); - return errorFromReturn("fchownat-opathdirfd", rc); - })); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/chroot.cc b/test/syscalls/linux/chroot.cc deleted file mode 100644 index fab79d300..000000000 --- a/test/syscalls/linux/chroot.cc +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <stddef.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <syscall.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/mount_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::HasSubstr; -using ::testing::Not; - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ChrootTest, Success) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - const auto rest = [] { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST(ChrootTest, PermissionDenied) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission - // on directories. - AutoCapability cap_search(CAP_DAC_READ_SEARCH, false); - AutoCapability cap_override(CAP_DAC_OVERRIDE, false); - - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */)); - EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES)); -} - -TEST(ChrootTest, NotDir) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - EXPECT_THAT(chroot(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(ChrootTest, NotExist) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - EXPECT_THAT(chroot("/foo/bar"), SyscallFailsWithErrno(ENOENT)); -} - -TEST(ChrootTest, WithoutCapability) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETPCAP))); - - // Unset CAP_SYS_CHROOT. - AutoCapability cap(CAP_SYS_CHROOT, false); - - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EPERM)); -} - -TEST(ChrootTest, CreatesNewRoot) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - // Grab the initial cwd. - char initial_cwd[1024]; - ASSERT_THAT(syscall(__NR_getcwd, initial_cwd, sizeof(initial_cwd)), - SyscallSucceeds()); - - auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto file_in_new_root = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path())); - - const auto rest = [&] { - // chroot into new_root. - TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); - - // getcwd should return "(unreachable)" followed by the initial_cwd. - char cwd[1024]; - TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); - std::string expected_cwd = "(unreachable)"; - expected_cwd += initial_cwd; - TEST_CHECK(strcmp(cwd, expected_cwd.c_str()) == 0); - - // Should not be able to stat file by its full path. - struct stat statbuf; - TEST_CHECK_ERRNO(stat(file_in_new_root.path().c_str(), &statbuf), ENOENT); - - // Should be able to stat file at new rooted path. - auto basename = std::string(Basename(file_in_new_root.path())); - auto rootedFile = "/" + basename; - TEST_CHECK_SUCCESS(stat(rootedFile.c_str(), &statbuf)); - - // Should be able to stat cwd at '.' even though it's outside root. - TEST_CHECK_SUCCESS(stat(".", &statbuf)); - - // chdir into new root. - TEST_CHECK_SUCCESS(chdir("/")); - - // getcwd should return "/". - TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); - TEST_CHECK_SUCCESS(strcmp(cwd, "/") == 0); - - // Statting '.', '..', '/', and '/..' all return the same dev and inode. - struct stat statbuf_dot; - TEST_CHECK_SUCCESS(stat(".", &statbuf_dot)); - struct stat statbuf_dotdot; - TEST_CHECK_SUCCESS(stat("..", &statbuf_dotdot)); - TEST_CHECK(statbuf_dot.st_dev == statbuf_dotdot.st_dev); - TEST_CHECK(statbuf_dot.st_ino == statbuf_dotdot.st_ino); - struct stat statbuf_slash; - TEST_CHECK_SUCCESS(stat("/", &statbuf_slash)); - TEST_CHECK(statbuf_dot.st_dev == statbuf_slash.st_dev); - TEST_CHECK(statbuf_dot.st_ino == statbuf_slash.st_ino); - struct stat statbuf_slashdotdot; - TEST_CHECK_SUCCESS(stat("/..", &statbuf_slashdotdot)); - TEST_CHECK(statbuf_dot.st_dev == statbuf_slashdotdot.st_dev); - TEST_CHECK(statbuf_dot.st_ino == statbuf_slashdotdot.st_ino); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST(ChrootTest, DotDotFromOpenFD) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - auto dir_outside_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY)); - auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - const auto rest = [&] { - // chroot into new_root. - TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); - - // openat on fd with path .. will succeed. - int other_fd; - TEST_CHECK_SUCCESS(other_fd = openat(fd.get(), "..", O_RDONLY)); - TEST_CHECK_SUCCESS(close(other_fd)); - - // getdents on fd should not error. - char buf[1024]; - TEST_CHECK_SUCCESS(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf))); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// Test that link resolution in a chroot can escape the root by following an -// open proc fd. Regression test for b/32316719. -TEST(ChrootTest, ProcFdLinkResolutionInChroot) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - const TempPath file_outside_chroot = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file_outside_chroot.path(), O_RDONLY)); - - const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); - - const auto rest = [&] { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); - - // Opening relative to an already open fd to a node outside the chroot - // works. - const FileDescriptor proc_self_fd = TEST_CHECK_NO_ERRNO_AND_VALUE( - OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); - - // Proc fd symlinks can escape the chroot if the fd the symlink refers to - // refers to an object outside the chroot. - struct stat s = {}; - TEST_CHECK_SUCCESS( - fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0)); - - // Try to stat the stdin fd. Internally, this is handled differently from a - // proc fd entry pointing to a file, since stdin is backed by a host fd, and - // isn't a walkable path on the filesystem inside the sandbox. - TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), "0", &s, 0)); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// This test will verify that when you hold a fd to proc before entering -// a chroot that any files inside the chroot will appear rooted to the -// base chroot when examining /proc/self/fd/{num}. -TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - // Get a FD to /proc before we enter the chroot. - const FileDescriptor proc = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - - const auto rest = [&] { - // Create and enter a chroot directory. - const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); - - // Open a file inside the chroot at /foo. - const FileDescriptor foo = - TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); - - // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're - // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo. - const std::string fd_path = absl::StrCat("self/fd/", foo.get()); - char buf[1024] = {}; - size_t bytes_read = 0; - TEST_CHECK_SUCCESS(bytes_read = readlinkat(proc.get(), fd_path.c_str(), buf, - sizeof(buf) - 1)); - - // The link should resolve to something. - TEST_CHECK(bytes_read > 0); - - // Assert that the link doesn't contain the chroot path and is only /foo. - TEST_CHECK(strcmp(buf, "/foo") == 0); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// This test will verify that a file inside a chroot when mmapped will not -// expose the full file path via /proc/self/maps and instead honor the chroot. -TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - // Get a FD to /proc before we enter the chroot. - const FileDescriptor proc = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - - const auto rest = [&] { - // Create and enter a chroot directory. - const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); - - // Open a file inside the chroot at /foo. - const FileDescriptor foo = - TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); - - // Mmap the newly created file. - void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE, foo.get(), 0); - TEST_CHECK_SUCCESS(reinterpret_cast<int64_t>(foo_map)); - - // Always unmap. - auto cleanup_map = - Cleanup([&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); }); - - // Examine /proc/self/maps to be sure that /foo doesn't appear to be - // mapped with the full chroot path. - const FileDescriptor maps = TEST_CHECK_NO_ERRNO_AND_VALUE( - OpenAt(proc.get(), "self/maps", O_RDONLY)); - - size_t bytes_read = 0; - char buf[8 * 1024] = {}; - TEST_CHECK_SUCCESS(bytes_read = ReadFd(maps.get(), buf, sizeof(buf))); - - // The maps file should have something. - TEST_CHECK(bytes_read > 0); - - // Finally we want to make sure the maps don't contain the chroot path - TEST_CHECK(std::string(buf, bytes_read).find(temp_dir.path()) == - std::string::npos); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// Test that mounts outside the chroot will not appear in /proc/self/mounts or -// /proc/self/mountinfo. -TEST(ChrootTest, ProcMountsMountinfoNoEscape) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - - // Create nested tmpfs mounts. - auto const outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("none", outer_dir.path(), "tmpfs", 0, "mode=0700", 0)); - - auto const inner_dir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir.path())); - auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("none", inner_dir.path(), "tmpfs", 0, "mode=0700", 0)); - - const auto rest = [&outer_dir, &inner_dir] { - // Filenames that will be checked for mounts, all relative to /proc dir. - std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; - - for (const std::string& path : paths) { - // We should have both inner and outer mounts. - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); - EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()), - HasSubstr(inner_dir.path()))); - // We better have at least two mounts: the mounts we created plus the - // root. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() > 2); - } - - // Get a FD to /proc before we enter the chroot. - const FileDescriptor proc = - TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - - // Chroot to outer mount. - TEST_CHECK_SUCCESS(chroot(outer_dir.path().c_str())); - - for (const std::string& path : paths) { - const FileDescriptor proc_file = - TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); - - // Only two mounts visible from this chroot: the inner and outer. Both - // paths should be relative to the new chroot. - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); - EXPECT_THAT(contents, - AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))), - Not(HasSubstr(outer_dir.path())), - Not(HasSubstr(inner_dir.path())))); - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() == 2); - } - - // Chroot to inner mount. We must use an absolute path accessible to our - // chroot. - const std::string inner_dir_basename = - absl::StrCat("/", Basename(inner_dir.path())); - TEST_CHECK_SUCCESS(chroot(inner_dir_basename.c_str())); - - for (const std::string& path : paths) { - const FileDescriptor proc_file = - TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); - const std::string contents = - TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); - - // Only the inner mount visible from this chroot. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - TEST_CHECK(submounts.size() == 1); - } - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/clock_getres.cc b/test/syscalls/linux/clock_getres.cc deleted file mode 100644 index c408b936c..000000000 --- a/test/syscalls/linux/clock_getres.cc +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/time.h> -#include <time.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// clock_getres works regardless of whether or not a timespec is passed. -TEST(ClockGetres, Timespec) { - struct timespec ts; - EXPECT_THAT(clock_getres(CLOCK_MONOTONIC, &ts), SyscallSucceeds()); - EXPECT_THAT(clock_getres(CLOCK_MONOTONIC, nullptr), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/clock_gettime.cc b/test/syscalls/linux/clock_gettime.cc deleted file mode 100644 index 7f6015049..000000000 --- a/test/syscalls/linux/clock_gettime.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <pthread.h> -#include <sys/time.h> - -#include <cerrno> -#include <cstdint> -#include <ctime> -#include <list> -#include <memory> -#include <string> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -int64_t clock_gettime_nsecs(clockid_t id) { - struct timespec ts; - TEST_PCHECK(clock_gettime(id, &ts) == 0); - return (ts.tv_sec * 1000000000 + ts.tv_nsec); -} - -// Spin on the CPU for at least ns nanoseconds, based on -// CLOCK_THREAD_CPUTIME_ID. -void spin_ns(int64_t ns) { - int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID); - int64_t end = start + ns; - - do { - constexpr int kLoopCount = 1000000; // large and arbitrary - // volatile to prevent the compiler from skipping this loop. - for (volatile int i = 0; i < kLoopCount; i++) { - } - } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end); -} - -// Test that CLOCK_PROCESS_CPUTIME_ID is a superset of CLOCK_THREAD_CPUTIME_ID. -TEST(ClockGettime, CputimeId) { - constexpr int kNumThreads = 13; // arbitrary - - absl::Duration spin_time = absl::Seconds(1); - - // Start off the worker threads and compute the aggregate time spent by - // the workers. Note that we test CLOCK_PROCESS_CPUTIME_ID by having the - // workers execute in parallel and verifying that CLOCK_PROCESS_CPUTIME_ID - // accumulates the runtime of all threads. - int64_t start = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID); - - // Create a kNumThreads threads. - std::list<ScopedThread> threads; - for (int i = 0; i < kNumThreads; i++) { - threads.emplace_back( - [spin_time] { spin_ns(absl::ToInt64Nanoseconds(spin_time)); }); - } - for (auto& t : threads) { - t.Join(); - } - - int64_t end = clock_gettime_nsecs(CLOCK_PROCESS_CPUTIME_ID); - - // The aggregate time spent in the worker threads must be at least - // 'kNumThreads' times the time each thread spun. - ASSERT_GE(end - start, kNumThreads * absl::ToInt64Nanoseconds(spin_time)); -} - -TEST(ClockGettime, JavaThreadTime) { - clockid_t clockid; - ASSERT_EQ(0, pthread_getcpuclockid(pthread_self(), &clockid)); - struct timespec tp; - ASSERT_THAT(clock_getres(clockid, &tp), SyscallSucceeds()); - EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0); - // A thread cputime is updated each 10msec and there is no approximation - // if a task is running. - do { - ASSERT_THAT(clock_gettime(clockid, &tp), SyscallSucceeds()); - } while (tp.tv_sec == 0 && tp.tv_nsec == 0); - EXPECT_TRUE(tp.tv_sec > 0 || tp.tv_nsec > 0); -} - -// There is not much to test here, since CLOCK_REALTIME may be discontiguous. -TEST(ClockGettime, RealtimeWorks) { - struct timespec tp; - EXPECT_THAT(clock_gettime(CLOCK_REALTIME, &tp), SyscallSucceeds()); -} - -class MonotonicClockTest : public ::testing::TestWithParam<clockid_t> {}; - -TEST_P(MonotonicClockTest, IsMonotonic) { - auto end = absl::Now() + absl::Seconds(5); - - struct timespec tp; - EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds()); - - auto prev = absl::TimeFromTimespec(tp); - while (absl::Now() < end) { - EXPECT_THAT(clock_gettime(GetParam(), &tp), SyscallSucceeds()); - auto now = absl::TimeFromTimespec(tp); - EXPECT_GE(now, prev); - prev = now; - } -} - -std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { - switch (info.param) { - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_BOOTTIME: - // CLOCK_BOOTTIME is a monotonic clock. - return "CLOCK_BOOTTIME"; - default: - return absl::StrCat(info.param); - } -} - -INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicClockTest, - ::testing::Values(CLOCK_MONOTONIC, - CLOCK_MONOTONIC_COARSE, - CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME), - PrintClockId); - -TEST(ClockGettime, UnimplementedReturnsEINVAL) { - SKIP_IF(!IsRunningOnGvisor()); - - struct timespec tp; - EXPECT_THAT(clock_gettime(CLOCK_REALTIME_ALARM, &tp), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME_ALARM, &tp), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(ClockGettime, InvalidClockIDReturnsEINVAL) { - struct timespec tp; - EXPECT_THAT(clock_gettime(-1, &tp), SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/clock_nanosleep.cc b/test/syscalls/linux/clock_nanosleep.cc deleted file mode 100644 index b55cddc52..000000000 --- a/test/syscalls/linux/clock_nanosleep.cc +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <time.h> - -#include <atomic> -#include <utility> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// sys_clock_nanosleep is defined because the glibc clock_nanosleep returns -// error numbers directly and does not set errno. This makes our Syscall -// matchers look a little weird when expecting failure: -// "SyscallSucceedsWithValue(ERRNO)". -int sys_clock_nanosleep(clockid_t clkid, int flags, - const struct timespec* request, - struct timespec* remain) { - return syscall(SYS_clock_nanosleep, clkid, flags, request, remain); -} - -PosixErrorOr<absl::Time> GetTime(clockid_t clk) { - struct timespec ts = {}; - const int rc = clock_gettime(clk, &ts); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "clock_gettime"); - } - return absl::TimeFromTimespec(ts); -} - -class WallClockNanosleepTest : public ::testing::TestWithParam<clockid_t> {}; - -TEST_P(WallClockNanosleepTest, InvalidValues) { - const struct timespec invalid[] = { - {.tv_sec = -1, .tv_nsec = -1}, {.tv_sec = 0, .tv_nsec = INT32_MIN}, - {.tv_sec = 0, .tv_nsec = INT32_MAX}, {.tv_sec = 0, .tv_nsec = -1}, - {.tv_sec = -1, .tv_nsec = 0}, - }; - - for (auto const ts : invalid) { - EXPECT_THAT(sys_clock_nanosleep(GetParam(), 0, &ts, nullptr), - SyscallFailsWithErrno(EINVAL)); - } -} - -TEST_P(WallClockNanosleepTest, SleepOneSecond) { - constexpr absl::Duration kSleepDuration = absl::Seconds(1); - struct timespec duration = absl::ToTimespec(kSleepDuration); - - const absl::Time before = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - EXPECT_THAT( - RetryEINTR(sys_clock_nanosleep)(GetParam(), 0, &duration, &duration), - SyscallSucceeds()); - const absl::Time after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - - EXPECT_GE(after - before, kSleepDuration); -} - -TEST_P(WallClockNanosleepTest, InterruptedNanosleep) { - constexpr absl::Duration kSleepDuration = absl::Seconds(60); - struct timespec duration = absl::ToTimespec(kSleepDuration); - - // Install no-op signal handler for SIGALRM. - struct sigaction sa = {}; - sigfillset(&sa.sa_mask); - sa.sa_handler = +[](int signo) {}; - const auto cleanup_sa = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Measure time since setting the alarm, since the alarm will interrupt the - // sleep and hence determine how long we sleep. - const absl::Time before = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - - // Set an alarm to go off while sleeping. - struct itimerval timer = {}; - timer.it_value.tv_sec = 1; - timer.it_value.tv_usec = 0; - timer.it_interval.tv_sec = 1; - timer.it_interval.tv_usec = 0; - const auto cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, timer)); - - EXPECT_THAT(sys_clock_nanosleep(GetParam(), 0, &duration, &duration), - SyscallFailsWithErrno(EINTR)); - const absl::Time after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - - // Remaining time updated. - const absl::Duration remaining = absl::DurationFromTimespec(duration); - EXPECT_GE(after - before + remaining, kSleepDuration); -} - -// Remaining time is *not* updated if nanosleep completes uninterrupted. -TEST_P(WallClockNanosleepTest, UninterruptedNanosleep) { - constexpr absl::Duration kSleepDuration = absl::Milliseconds(10); - const struct timespec duration = absl::ToTimespec(kSleepDuration); - - while (true) { - constexpr int kRemainingMagic = 42; - struct timespec remaining; - remaining.tv_sec = kRemainingMagic; - remaining.tv_nsec = kRemainingMagic; - - int ret = sys_clock_nanosleep(GetParam(), 0, &duration, &remaining); - if (ret == EINTR) { - // Retry from beginning. We want a single uninterrupted call. - continue; - } - - EXPECT_THAT(ret, SyscallSucceeds()); - EXPECT_EQ(remaining.tv_sec, kRemainingMagic); - EXPECT_EQ(remaining.tv_nsec, kRemainingMagic); - break; - } -} - -TEST_P(WallClockNanosleepTest, SleepUntil) { - const absl::Time now = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - const absl::Time until = now + absl::Seconds(2); - const struct timespec ts = absl::ToTimespec(until); - - EXPECT_THAT( - RetryEINTR(sys_clock_nanosleep)(GetParam(), TIMER_ABSTIME, &ts, nullptr), - SyscallSucceeds()); - const absl::Time after = ASSERT_NO_ERRNO_AND_VALUE(GetTime(GetParam())); - - EXPECT_GE(after, until); -} - -INSTANTIATE_TEST_SUITE_P(Sleepers, WallClockNanosleepTest, - ::testing::Values(CLOCK_REALTIME, CLOCK_MONOTONIC)); - -TEST(ClockNanosleepProcessTest, SleepFiveSeconds) { - const absl::Duration kSleepDuration = absl::Seconds(5); - struct timespec duration = absl::ToTimespec(kSleepDuration); - - // Ensure that CLOCK_PROCESS_CPUTIME_ID advances. - std::atomic<bool> done(false); - ScopedThread t([&] { - while (!done.load()) { - } - }); - const auto cleanup_done = Cleanup([&] { done.store(true); }); - - const absl::Time before = - ASSERT_NO_ERRNO_AND_VALUE(GetTime(CLOCK_PROCESS_CPUTIME_ID)); - EXPECT_THAT(RetryEINTR(sys_clock_nanosleep)(CLOCK_PROCESS_CPUTIME_ID, 0, - &duration, &duration), - SyscallSucceeds()); - const absl::Time after = - ASSERT_NO_ERRNO_AND_VALUE(GetTime(CLOCK_PROCESS_CPUTIME_ID)); - EXPECT_GE(after - before, kSleepDuration); -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/concurrency.cc b/test/syscalls/linux/concurrency.cc deleted file mode 100644 index 7cd6a75bd..000000000 --- a/test/syscalls/linux/concurrency.cc +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> - -#include <atomic> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/platform_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -// Test that a thread that never yields to the OS does not prevent other threads -// from running. -TEST(ConcurrencyTest, SingleProcessMultithreaded) { - std::atomic<int> a(0); - - ScopedThread t([&a]() { - while (!a.load()) { - } - }); - - absl::SleepFor(absl::Seconds(1)); - - // We are still able to execute code in this thread. The other hasn't - // permanently hung execution in both threads. - a.store(1); -} - -// Test that multiple threads in this process continue to execute in parallel, -// even if an unrelated second process is spawned. Regression test for -// b/32119508. -TEST(ConcurrencyTest, MultiProcessMultithreaded) { - // In PID 1, start TIDs 1 and 2, and put both to sleep. - // - // Start PID 3, which spins for 5 seconds, then exits. - // - // TIDs 1 and 2 wake and attempt to Activate, which cannot occur until PID 3 - // exits. - // - // Both TIDs 1 and 2 should be woken. If they are not both woken, the test - // hangs. - // - // This is all fundamentally racy. If we are failing to wake all threads, the - // expectation is that this test becomes flaky, rather than consistently - // failing. - // - // If additional background threads fail to block, we may never schedule the - // child, at which point this test effectively becomes - // MultiProcessConcurrency. That's not expected to occur. - - std::atomic<int> a(0); - ScopedThread t([&a]() { - // Block so that PID 3 can execute and we can wait on its exit. - absl::SleepFor(absl::Seconds(1)); - while (!a.load()) { - } - }); - - pid_t child_pid = fork(); - if (child_pid == 0) { - // Busy wait without making any blocking syscalls. - auto end = absl::Now() + absl::Seconds(5); - while (absl::Now() < end) { - } - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - absl::SleepFor(absl::Seconds(1)); - - // If only TID 1 is woken, thread.Join will hang. - // If only TID 2 is woken, both will hang. - a.store(1); - t.Join(); - - int status = 0; - EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(WEXITSTATUS(status), 0); -} - -// Test that multiple processes can execute concurrently, even if one process -// never yields. -TEST(ConcurrencyTest, MultiProcessConcurrency) { - SKIP_IF(PlatformSupportMultiProcess() == PlatformSupport::NotSupported); - - pid_t child_pid = fork(); - if (child_pid == 0) { - while (true) { - } - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - absl::SleepFor(absl::Seconds(5)); - - // We are still able to execute code in this process. The other hasn't - // permanently hung execution in both processes. - ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - int status = 0; - - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - ASSERT_TRUE(WIFSIGNALED(status)); - ASSERT_EQ(WTERMSIG(status), SIGKILL); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/connect_external.cc b/test/syscalls/linux/connect_external.cc deleted file mode 100644 index 1edb50e47..000000000 --- a/test/syscalls/linux/connect_external.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <stdlib.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <string> -#include <tuple> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/test_util.h" - -// This file contains tests specific to connecting to host UDS managed outside -// the sandbox / test. -// -// A set of ultity sockets will be created externally in $TEST_UDS_TREE and -// $TEST_UDS_ATTACH_TREE for these tests to interact with. - -namespace gvisor { -namespace testing { - -namespace { - -struct ProtocolSocket { - int protocol; - std::string name; -}; - -// Parameter is (socket root dir, ProtocolSocket). -using GoferStreamSeqpacketTest = - ::testing::TestWithParam<std::tuple<std::string, ProtocolSocket>>; - -// Connect to a socket and verify that write/read work. -// -// An "echo" socket doesn't work for dgram sockets because our socket is -// unnamed. The server thus has no way to reply to us. -TEST_P(GoferStreamSeqpacketTest, Echo) { - std::string env; - ProtocolSocket proto; - std::tie(env, proto) = GetParam(); - - char* val = getenv(env.c_str()); - ASSERT_NE(val, nullptr); - std::string root(val); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, proto.protocol, 0)); - - std::string socket_path = JoinPath(root, proto.name, "echo"); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, socket_path.c_str(), socket_path.length()); - - ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - constexpr int kBufferSize = 64; - char send_buffer[kBufferSize]; - memset(send_buffer, 'a', sizeof(send_buffer)); - - ASSERT_THAT(WriteFd(sock.get(), send_buffer, sizeof(send_buffer)), - SyscallSucceedsWithValue(sizeof(send_buffer))); - - char recv_buffer[kBufferSize]; - ASSERT_THAT(ReadFd(sock.get(), recv_buffer, sizeof(recv_buffer)), - SyscallSucceedsWithValue(sizeof(recv_buffer))); - ASSERT_EQ(0, memcmp(send_buffer, recv_buffer, sizeof(send_buffer))); -} - -// It is not possible to connect to a bound but non-listening socket. -TEST_P(GoferStreamSeqpacketTest, NonListening) { - std::string env; - ProtocolSocket proto; - std::tie(env, proto) = GetParam(); - - char* val = getenv(env.c_str()); - ASSERT_NE(val, nullptr); - std::string root(val); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, proto.protocol, 0)); - - std::string socket_path = JoinPath(root, proto.name, "nonlistening"); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, socket_path.c_str(), socket_path.length()); - - ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -INSTANTIATE_TEST_SUITE_P( - StreamSeqpacket, GoferStreamSeqpacketTest, - ::testing::Combine( - // Test access via standard path and attach point. - ::testing::Values("TEST_UDS_TREE", "TEST_UDS_ATTACH_TREE"), - ::testing::Values(ProtocolSocket{SOCK_STREAM, "stream"}, - ProtocolSocket{SOCK_SEQPACKET, "seqpacket"}))); - -// Parameter is socket root dir. -using GoferDgramTest = ::testing::TestWithParam<std::string>; - -// Connect to a socket and verify that write works. -// -// An "echo" socket doesn't work for dgram sockets because our socket is -// unnamed. The server thus has no way to reply to us. -TEST_P(GoferDgramTest, Null) { - std::string env = GetParam(); - char* val = getenv(env.c_str()); - ASSERT_NE(val, nullptr); - std::string root(val); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_DGRAM, 0)); - - std::string socket_path = JoinPath(root, "dgram/null"); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, socket_path.c_str(), socket_path.length()); - - ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - constexpr int kBufferSize = 64; - char send_buffer[kBufferSize]; - memset(send_buffer, 'a', sizeof(send_buffer)); - - ASSERT_THAT(WriteFd(sock.get(), send_buffer, sizeof(send_buffer)), - SyscallSucceedsWithValue(sizeof(send_buffer))); -} - -INSTANTIATE_TEST_SUITE_P(Dgram, GoferDgramTest, - // Test access via standard path and attach point. - ::testing::Values("TEST_UDS_TREE", - "TEST_UDS_ATTACH_TREE")); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/creat.cc b/test/syscalls/linux/creat.cc deleted file mode 100644 index 3c270d6da..000000000 --- a/test/syscalls/linux/creat.cc +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kMode = 0666; - -TEST(CreatTest, CreatCreatesNewFile) { - std::string const path = NewTempAbsPath(); - struct stat buf; - int fd; - ASSERT_THAT(stat(path.c_str(), &buf), SyscallFailsWithErrno(ENOENT)); - ASSERT_THAT(fd = creat(path.c_str(), kMode), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - EXPECT_THAT(stat(path.c_str(), &buf), SyscallSucceeds()); -} - -TEST(CreatTest, CreatTruncatesExistingFile) { - auto temp_path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - int fd; - ASSERT_NO_ERRNO(SetContents(temp_path.path(), "non-empty")); - ASSERT_THAT(fd = creat(temp_path.path().c_str(), kMode), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - std::string new_contents; - ASSERT_NO_ERRNO(GetContents(temp_path.path(), &new_contents)); - EXPECT_EQ("", new_contents); -} - -TEST(CreatTest, CreatWithNameTooLong) { - // Start with a unique name, and pad it to NAME_MAX + 1; - std::string name = NewTempRelPath(); - int padding = (NAME_MAX + 1) - name.size(); - name.append(padding, 'x'); - const std::string& path = JoinPath(GetAbsoluteTestTmpdir(), name); - - // Creation should return ENAMETOOLONG. - ASSERT_THAT(creat(path.c_str(), kMode), SyscallFailsWithErrno(ENAMETOOLONG)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/dev.cc b/test/syscalls/linux/dev.cc deleted file mode 100644 index 1d0d584cd..000000000 --- a/test/syscalls/linux/dev.cc +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(DevTest, LseekDevUrandom) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/urandom", O_RDONLY)); - EXPECT_THAT(lseek(fd.get(), -10, SEEK_CUR), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), -10, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); -} - -TEST(DevTest, LseekDevNull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - EXPECT_THAT(lseek(fd.get(), -10, SEEK_CUR), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), -10, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); -} - -TEST(DevTest, LseekDevZero) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); -} - -TEST(DevTest, LseekDevFull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_RDONLY)); - EXPECT_THAT(lseek(fd.get(), 123, SEEK_SET), SyscallSucceedsWithValue(0)); - EXPECT_THAT(lseek(fd.get(), 123, SEEK_CUR), SyscallSucceedsWithValue(0)); - EXPECT_THAT(lseek(fd.get(), 123, SEEK_END), SyscallSucceedsWithValue(0)); -} - -TEST(DevTest, LseekDevNullFreshFile) { - // Seeks to /dev/null always return 0. - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - - EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - EXPECT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceedsWithValue(0)); - EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - const FileDescriptor fd3 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - EXPECT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); -} - -TEST(DevTest, OpenTruncate) { - // Truncation is ignored on linux and gvisor for device files. - ASSERT_NO_ERRNO_AND_VALUE( - Open("/dev/null", O_CREAT | O_TRUNC | O_WRONLY, 0644)); - ASSERT_NO_ERRNO_AND_VALUE( - Open("/dev/zero", O_CREAT | O_TRUNC | O_WRONLY, 0644)); - ASSERT_NO_ERRNO_AND_VALUE( - Open("/dev/full", O_CREAT | O_TRUNC | O_WRONLY, 0644)); -} - -TEST(DevTest, Pread64DevNull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - char buf[1]; - EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(0)); -} - -TEST(DevTest, Pread64DevZero) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - char buf[1]; - EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(1)); -} - -TEST(DevTest, Pread64DevFull) { - // /dev/full behaves like /dev/zero with respect to reads. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_RDONLY)); - char buf[1]; - EXPECT_THAT(pread64(fd.get(), buf, 1, 0), SyscallSucceedsWithValue(1)); -} - -TEST(DevTest, ReadDevNull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDONLY)); - std::vector<char> buf(1); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallSucceeds()); -} - -// Do not allow random save as it could lead to partial reads. -TEST(DevTest, ReadDevZero_NoRandomSave) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - - constexpr int kReadSize = 128 * 1024; - std::vector<char> buf(kReadSize, 1); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), kReadSize), - SyscallSucceedsWithValue(kReadSize)); - EXPECT_EQ(std::vector<char>(kReadSize, 0), buf); -} - -TEST(DevTest, WriteDevNull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_WRONLY)); - EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallSucceedsWithValue(1)); -} - -TEST(DevTest, WriteDevZero) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallSucceedsWithValue(1)); -} - -TEST(DevTest, WriteDevFull) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/full", O_WRONLY)); - EXPECT_THAT(WriteFd(fd.get(), "a", 1), SyscallFailsWithErrno(ENOSPC)); -} - -TEST(DevTest, TTYExists) { - struct stat statbuf = {}; - ASSERT_THAT(stat("/dev/tty", &statbuf), SyscallSucceeds()); - // Check that it's a character device with rw-rw-rw- permissions. - EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666); -} - -TEST(DevTest, OpenDevFuse) { - // Note(gvisor.dev/issue/3076) This won't work in the sentry until the new - // device registration is complete. - SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor() || !IsFUSEEnabled()); - - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY)); -} - -TEST(DevTest, ReadDevFuseWithoutMount) { - // Note(gvisor.dev/issue/3076) This won't work in the sentry until the new - // device registration is complete. - SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY)); - - std::vector<char> buf(1); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), sizeof(buf)), - SyscallFailsWithErrno(EPERM)); -} - -} // namespace -} // namespace testing - -} // namespace gvisor diff --git a/test/syscalls/linux/dup.cc b/test/syscalls/linux/dup.cc deleted file mode 100644 index ba4e13fb9..000000000 --- a/test/syscalls/linux/dup.cc +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<FileDescriptor> Dup2(const FileDescriptor& fd, int target_fd) { - int new_fd = dup2(fd.get(), target_fd); - if (new_fd < 0) { - return PosixError(errno, "Dup2"); - } - return FileDescriptor(new_fd); -} - -PosixErrorOr<FileDescriptor> Dup3(const FileDescriptor& fd, int target_fd, - int flags) { - int new_fd = dup3(fd.get(), target_fd, flags); - if (new_fd < 0) { - return PosixError(errno, "Dup2"); - } - return FileDescriptor(new_fd); -} - -TEST(DupTest, Dup) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Dup the descriptor and make sure it's the same file. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); -} - -TEST(DupTest, DupClearsCloExec) { - // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set. - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC)); - EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - - // Duplicate the descriptor. Ensure that it doesn't have FD_CLOEXEC set. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); -} - -TEST(DupTest, DupWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); - int flags; - ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - - // Dup the descriptor and make sure it's the same file. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); -} - -TEST(DupTest, Dup2) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Regular dup once. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - - // Dup over the file above. - int target_fd = nfd.release(); - FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); - EXPECT_EQ(target_fd, nfd2.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); -} - -TEST(DupTest, Dup2SameFD) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Should succeed. - ASSERT_THAT(dup2(fd.get(), fd.get()), SyscallSucceedsWithValue(fd.get())); -} - -TEST(DupTest, Dup2WithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); - int flags; - ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - - // Regular dup once. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); - - // Dup over the file above. - int target_fd = nfd.release(); - FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); - EXPECT_EQ(target_fd, nfd2.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); - EXPECT_THAT(fcntl(nfd2.get(), F_GETFL), SyscallSucceedsWithValue(flags)); -} - -TEST(DupTest, Dup3) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Regular dup once. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - - // Dup over the file above, check that it has no CLOEXEC. - nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - - // Dup over the file again, check that it does not CLOEXEC. - nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST(DupTest, Dup3FailsSameFD) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Only dup3 fails if the new and old fd are the same. - ASSERT_THAT(dup3(fd.get(), fd.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(DupTest, Dup3WithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); - EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - int flags; - ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - - // Regular dup once. - FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - - // Dup over the file above, check that it has no CLOEXEC. - nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); - - // Dup over the file again, check that it does not CLOEXEC. - nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc deleted file mode 100644 index 8a72ef10a..000000000 --- a/test/syscalls/linux/epoll.cc +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <limits.h> -#include <pthread.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> -#include <sys/epoll.h> -#include <sys/eventfd.h> -#include <time.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/epoll_util.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kFDsPerEpoll = 3; -constexpr uint64_t kMagicConstant = 0x0102030405060708; - -TEST(EpollTest, AllWritable) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), - EPOLLIN | EPOLLOUT, kMagicConstant + i)); - } - - struct epoll_event result[kFDsPerEpoll]; - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(kFDsPerEpoll)); - for (int i = 0; i < kFDsPerEpoll; i++) { - ASSERT_EQ(result[i].events, EPOLLOUT); - } -} - -TEST(EpollTest, LastReadable) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), - EPOLLIN | EPOLLOUT, kMagicConstant + i)); - } - - uint64_t tmp = 1; - ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - - struct epoll_event result[kFDsPerEpoll]; - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(kFDsPerEpoll)); - - int i; - for (i = 0; i < kFDsPerEpoll - 1; i++) { - EXPECT_EQ(result[i].events, EPOLLOUT); - } - EXPECT_EQ(result[i].events, EPOLLOUT | EPOLLIN); - EXPECT_EQ(result[i].data.u64, kMagicConstant + i); -} - -TEST(EpollTest, LastNonWritable) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), - EPOLLIN | EPOLLOUT, kMagicConstant + i)); - } - - // Write the maximum value to the event fd so that writing to it again would - // block. - uint64_t tmp = ULLONG_MAX - 1; - ASSERT_THAT(WriteFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - - struct epoll_event result[kFDsPerEpoll]; - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(kFDsPerEpoll)); - - int i; - for (i = 0; i < kFDsPerEpoll - 1; i++) { - EXPECT_EQ(result[i].events, EPOLLOUT); - } - EXPECT_EQ(result[i].events, EPOLLIN); - EXPECT_THAT(ReadFd(eventfds[kFDsPerEpoll - 1].get(), &tmp, sizeof(tmp)), - sizeof(tmp)); - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(kFDsPerEpoll)); - - for (i = 0; i < kFDsPerEpoll; i++) { - EXPECT_EQ(result[i].events, EPOLLOUT); - } -} - -TEST(EpollTest, Timeout_NoRandomSave) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, - kMagicConstant + i)); - } - - constexpr int kTimeoutMs = 200; - struct timespec begin; - struct timespec end; - struct epoll_event result[kFDsPerEpoll]; - - { - const DisableSave ds; // Timing-related. - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds()); - - ASSERT_THAT( - RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, kTimeoutMs), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds()); - } - - // Check the lower bound on the timeout. Checking for an upper bound is - // fragile because Linux can overrun the timeout due to scheduling delays. - EXPECT_GT(ms_elapsed(begin, end), kTimeoutMs - 1); -} - -void* writer(void* arg) { - int fd = *reinterpret_cast<int*>(arg); - uint64_t tmp = 1; - - usleep(200000); - if (WriteFd(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) { - fprintf(stderr, "writer failed: errno %s\n", strerror(errno)); - } - - return nullptr; -} - -TEST(EpollTest, WaitThenUnblock) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, - kMagicConstant + i)); - } - - // Fire off a thread that will make at least one of the event fds readable. - pthread_t thread; - int make_readable = eventfds[0].get(); - ASSERT_THAT(pthread_create(&thread, nullptr, writer, &make_readable), - SyscallSucceedsWithValue(0)); - - struct epoll_event result[kFDsPerEpoll]; - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); -} - -void sighandler(int s) {} - -void* signaler(void* arg) { - pthread_t* t = reinterpret_cast<pthread_t*>(arg); - // Repeatedly send the real-time signal until we are detached, because it's - // difficult to know exactly when epoll_wait on another thread (which this - // is intending to interrupt) has started blocking. - while (1) { - usleep(200000); - pthread_kill(*t, SIGRTMIN); - } - return nullptr; -} - -TEST(EpollTest, UnblockWithSignal) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, - kMagicConstant + i)); - } - - signal(SIGRTMIN, sighandler); - // Unblock the real time signals that InitGoogle blocks :( - sigset_t unblock; - sigemptyset(&unblock); - sigaddset(&unblock, SIGRTMIN); - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &unblock, nullptr), SyscallSucceeds()); - - pthread_t thread; - pthread_t cur = pthread_self(); - ASSERT_THAT(pthread_create(&thread, nullptr, signaler, &cur), - SyscallSucceedsWithValue(0)); - - struct epoll_event result[kFDsPerEpoll]; - EXPECT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallFailsWithErrno(EINTR)); - EXPECT_THAT(pthread_cancel(thread), SyscallSucceeds()); - EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); -} - -TEST(EpollTest, TimeoutNoFds) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - struct epoll_event result[kFDsPerEpoll]; - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); -} - -struct addr_ctx { - int epollfd; - int eventfd; -}; - -void* fd_adder(void* arg) { - struct addr_ctx* actx = reinterpret_cast<struct addr_ctx*>(arg); - struct epoll_event event; - event.events = EPOLLIN | EPOLLOUT; - event.data.u64 = 0xdeadbeeffacefeed; - - usleep(200000); - if (epoll_ctl(actx->epollfd, EPOLL_CTL_ADD, actx->eventfd, &event) == -1) { - fprintf(stderr, "epoll_ctl failed: %s\n", strerror(errno)); - } - - return nullptr; -} - -TEST(EpollTest, UnblockWithNewFD) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - - pthread_t thread; - struct addr_ctx actx = {epollfd.get(), eventfd.get()}; - ASSERT_THAT(pthread_create(&thread, nullptr, fd_adder, &actx), - SyscallSucceedsWithValue(0)); - - struct epoll_event result[kFDsPerEpoll]; - // Wait while no FDs are ready, but after 200ms fd_adder will add a ready FD - // to epoll which will wake us up. - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_THAT(pthread_detach(thread), SyscallSucceeds()); - EXPECT_EQ(result[0].data.u64, 0xdeadbeeffacefeed); -} - -TEST(EpollTest, Oneshot) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - std::vector<FileDescriptor> eventfds; - for (int i = 0; i < kFDsPerEpoll; i++) { - eventfds.push_back(ASSERT_NO_ERRNO_AND_VALUE(NewEventFD())); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfds[i].get(), EPOLLIN, - kMagicConstant + i)); - } - - struct epoll_event event; - event.events = EPOLLOUT | EPOLLONESHOT; - event.data.u64 = kMagicConstant; - ASSERT_THAT( - epoll_ctl(epollfd.get(), EPOLL_CTL_MOD, eventfds[0].get(), &event), - SyscallSucceeds()); - - struct epoll_event result[kFDsPerEpoll]; - // One-shot entry means that the first epoll_wait should succeed. - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].data.u64, kMagicConstant); - - // One-shot entry means that the second epoll_wait should timeout. - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); -} - -TEST(EpollTest, EdgeTriggered_NoRandomSave) { - // Test edge-triggered entry: make it edge-triggered, first wait should - // return it, second one should time out, make it writable again, third wait - // should return it, fourth wait should timeout. - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(), - EPOLLOUT | EPOLLET, kMagicConstant)); - - struct epoll_event result[kFDsPerEpoll]; - - { - const DisableSave ds; // May trigger spurious event. - - // Edge-triggered entry means that the first epoll_wait should return the - // event. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].data.u64, kMagicConstant); - - // Edge-triggered entry means that the second epoll_wait should time out. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); - } - - uint64_t tmp = ULLONG_MAX - 1; - - // Make an fd non-writable. - ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - - // Make the same fd non-writable to trigger a change, which will trigger an - // edge-triggered event. - ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - - { - const DisableSave ds; // May trigger spurious event. - - // An edge-triggered event should now be returned. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].data.u64, kMagicConstant); - - // The edge-triggered event had been consumed above, we don't expect to - // get it again. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); - } -} - -TEST(EpollTest, OneshotAndEdgeTriggered) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), eventfd.get(), - EPOLLOUT | EPOLLET | EPOLLONESHOT, - kMagicConstant)); - - struct epoll_event result[kFDsPerEpoll]; - // First time one shot edge-triggered entry means that epoll_wait should - // return the event. - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].data.u64, kMagicConstant); - - // Edge-triggered entry means that the second epoll_wait should time out. - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); - - uint64_t tmp = ULLONG_MAX - 1; - // Make an fd non-writable. - ASSERT_THAT(WriteFd(eventfd.get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - // Make the same fd non-writable to trigger a change, which will not trigger - // an edge-triggered event because we've also included EPOLLONESHOT. - ASSERT_THAT(ReadFd(eventfd.get(), &tmp, sizeof(tmp)), - SyscallSucceedsWithValue(sizeof(tmp))); - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); -} - -TEST(EpollTest, CycleOfOneDisallowed) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - - struct epoll_event event; - event.events = EPOLLOUT; - event.data.u64 = kMagicConstant; - - ASSERT_THAT(epoll_ctl(epollfd.get(), EPOLL_CTL_ADD, epollfd.get(), &event), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(EpollTest, CycleOfThreeDisallowed) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto epollfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto epollfd2 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd.get(), epollfd1.get(), EPOLLIN, kMagicConstant)); - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd1.get(), epollfd2.get(), EPOLLIN, kMagicConstant)); - - struct epoll_event event; - event.events = EPOLLIN; - event.data.u64 = kMagicConstant; - EXPECT_THAT(epoll_ctl(epollfd2.get(), EPOLL_CTL_ADD, epollfd.get(), &event), - SyscallFailsWithErrno(ELOOP)); -} - -TEST(EpollTest, CloseFile) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd.get(), eventfd.get(), EPOLLOUT, kMagicConstant)); - - struct epoll_event result[kFDsPerEpoll]; - ASSERT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, -1), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].data.u64, kMagicConstant); - - // Close the event fd early. - eventfd.reset(); - - EXPECT_THAT(RetryEINTR(epoll_wait)(epollfd.get(), result, kFDsPerEpoll, 100), - SyscallSucceedsWithValue(0)); -} - -TEST(EpollTest, PipeReaderHupAfterWriterClosed) { - auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - int pipefds[2]; - ASSERT_THAT(pipe(pipefds), SyscallSucceeds()); - FileDescriptor rfd(pipefds[0]); - FileDescriptor wfd(pipefds[1]); - - ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), rfd.get(), 0, kMagicConstant)); - struct epoll_event result[kFDsPerEpoll]; - // Initially, rfd should not generate any events of interest. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 0), - SyscallSucceedsWithValue(0)); - // Close the write end of the pipe. - wfd.reset(); - // rfd should now generate EPOLLHUP, which EPOLL_CTL_ADD unconditionally adds - // to the set of events of interest. - ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 0), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(result[0].events, EPOLLHUP); - EXPECT_EQ(result[0].data.u64, kMagicConstant); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/eventfd.cc b/test/syscalls/linux/eventfd.cc deleted file mode 100644 index dc794415e..000000000 --- a/test/syscalls/linux/eventfd.cc +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <pthread.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/epoll.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/epoll_util.h" -#include "test/util/eventfd_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(EventfdTest, Nonblock) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t l; - ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallFailsWithErrno(EAGAIN)); - - l = 1; - ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds()); - - l = 0; - ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallSucceeds()); - EXPECT_EQ(l, 1); - - ASSERT_THAT(read(efd.get(), &l, sizeof(l)), SyscallFailsWithErrno(EAGAIN)); -} - -void* read_three_times(void* arg) { - int efd = *reinterpret_cast<int*>(arg); - uint64_t l; - EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l))); - EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l))); - EXPECT_THAT(read(efd, &l, sizeof(l)), SyscallSucceedsWithValue(sizeof(l))); - return nullptr; -} - -TEST(EventfdTest, BlockingWrite) { - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_SEMAPHORE)); - int efd = fd.get(); - - pthread_t p; - ASSERT_THAT(pthread_create(&p, nullptr, read_three_times, - reinterpret_cast<void*>(&efd)), - SyscallSucceeds()); - - uint64_t l = 1; - ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds()); - EXPECT_EQ(l, 1); - - ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds()); - EXPECT_EQ(l, 1); - - ASSERT_THAT(write(efd, &l, sizeof(l)), SyscallSucceeds()); - EXPECT_EQ(l, 1); - - ASSERT_THAT(pthread_join(p, nullptr), SyscallSucceeds()); -} - -TEST(EventfdTest, SmallWrite) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t l = 16; - ASSERT_THAT(write(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL)); -} - -TEST(EventfdTest, SmallRead) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t l = 1; - ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds()); - - l = 0; - ASSERT_THAT(read(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL)); -} - -TEST(EventfdTest, IllegalSeek) { - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - EXPECT_THAT(lseek(efd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); -} - -TEST(EventfdTest, IllegalPread) { - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - int l; - EXPECT_THAT(pread(efd.get(), &l, sizeof(l), 0), - SyscallFailsWithErrno(ESPIPE)); -} - -TEST(EventfdTest, IllegalPwrite) { - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - EXPECT_THAT(pwrite(efd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE)); -} - -TEST(EventfdTest, BigWrite) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t big[16]; - big[0] = 16; - ASSERT_THAT(write(efd.get(), big, sizeof(big)), SyscallSucceeds()); -} - -TEST(EventfdTest, BigRead) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t l = 1; - ASSERT_THAT(write(efd.get(), &l, sizeof(l)), SyscallSucceeds()); - - uint64_t big[16]; - ASSERT_THAT(read(efd.get(), big, sizeof(big)), SyscallSucceeds()); - EXPECT_EQ(big[0], 1); -} - -TEST(EventfdTest, BigWriteBigRead) { - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - - uint64_t l[16]; - l[0] = 16; - ASSERT_THAT(write(efd.get(), l, sizeof(l)), SyscallSucceeds()); - ASSERT_THAT(read(efd.get(), l, sizeof(l)), SyscallSucceeds()); - EXPECT_EQ(l[0], 1); -} - -TEST(EventfdTest, SpliceFromPipePartialSucceeds) { - int pipes[2]; - ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds()); - const FileDescriptor pipe_rfd(pipes[0]); - const FileDescriptor pipe_wfd(pipes[1]); - constexpr uint64_t kVal{1}; - - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK)); - - uint64_t event_array[2]; - event_array[0] = kVal; - event_array[1] = kVal; - ASSERT_THAT(write(pipe_wfd.get(), event_array, sizeof(event_array)), - SyscallSucceedsWithValue(sizeof(event_array))); - EXPECT_THAT(splice(pipe_rfd.get(), /*__offin=*/nullptr, efd.get(), - /*__offout=*/nullptr, sizeof(event_array[0]) + 1, - SPLICE_F_NONBLOCK), - SyscallSucceedsWithValue(sizeof(event_array[0]))); - - uint64_t val; - ASSERT_THAT(read(efd.get(), &val, sizeof(val)), - SyscallSucceedsWithValue(sizeof(val))); - EXPECT_EQ(val, kVal); -} - -// NotifyNonZero is inherently racy, so random save is disabled. -TEST(EventfdTest, NotifyNonZero_NoRandomSave) { - // Waits will time out at 10 seconds. - constexpr int kEpollTimeoutMs = 10000; - // Create an eventfd descriptor. - FileDescriptor efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(7, EFD_NONBLOCK | EFD_SEMAPHORE)); - // Create an epoll fd to listen to efd. - FileDescriptor epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - // Add efd to epoll. - ASSERT_NO_ERRNO( - RegisterEpollFD(epollfd.get(), efd.get(), EPOLLIN | EPOLLET, efd.get())); - - // Use epoll to get a value from efd. - struct epoll_event out_ev; - int wait_out = epoll_wait(epollfd.get(), &out_ev, 1, kEpollTimeoutMs); - EXPECT_EQ(wait_out, 1); - EXPECT_EQ(efd.get(), out_ev.data.fd); - uint64_t val = 0; - ASSERT_THAT(read(efd.get(), &val, sizeof(val)), SyscallSucceeds()); - EXPECT_EQ(val, 1); - - // Start a thread that, after this thread blocks on epoll_wait, will write to - // efd. This is racy -- it's possible that this write will happen after - // epoll_wait times out. - ScopedThread t([&efd] { - sleep(5); - uint64_t val = 1; - EXPECT_THAT(write(efd.get(), &val, sizeof(val)), - SyscallSucceedsWithValue(sizeof(val))); - }); - - // epoll_wait should return once the thread writes. - wait_out = epoll_wait(epollfd.get(), &out_ev, 1, kEpollTimeoutMs); - EXPECT_EQ(wait_out, 1); - EXPECT_EQ(efd.get(), out_ev.data.fd); - - val = 0; - ASSERT_THAT(read(efd.get(), &val, sizeof(val)), SyscallSucceeds()); - EXPECT_EQ(val, 1); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc deleted file mode 100644 index 11dc1c651..000000000 --- a/test/syscalls/linux/exceptions.cc +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/platform_util.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -#if defined(__x86_64__) -// Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5 -// "x87 FPU Control Word". -constexpr uint16_t kX87ControlWordDefault = 0x37f; - -// Mask for the divide-by-zero exception. -constexpr uint16_t kX87ControlWordDiv0Mask = 1 << 2; - -// Default value for the SSE control register (MXCSR). See Intel SDM Vol 1, Ch -// 11.6.4 "Initialization of SSE/SSE3 Extensions". -constexpr uint32_t kMXCSRDefault = 0x1f80; - -// Mask for the divide-by-zero exception. -constexpr uint32_t kMXCSRDiv0Mask = 1 << 9; - -// Flag for a pending divide-by-zero exception. -constexpr uint32_t kMXCSRDiv0Flag = 1 << 2; - -void inline Halt() { asm("hlt\r\n"); } - -void inline SetAlignmentCheck() { - asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber - "pushf\r\n" - "pop %%rax\r\n" - "or $0x40000, %%rax\r\n" - "push %%rax\r\n" - "popf\r\n" - "addq $128, %%rsp\r\n" - : - : - : "ax"); -} - -void inline ClearAlignmentCheck() { - asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber - "pushf\r\n" - "pop %%rax\r\n" - "mov $0x40000, %%rbx\r\n" - "not %%rbx\r\n" - "and %%rbx, %%rax\r\n" - "push %%rax\r\n" - "popf\r\n" - "addq $128, %%rsp\r\n" - : - : - : "ax", "bx"); -} - -void inline Int3Normal() { asm(".byte 0xcd, 0x03\r\n"); } - -void inline Int3Compact() { asm(".byte 0xcc\r\n"); } - -void InIOHelper(int width, int value) { - EXPECT_EXIT( - { - switch (width) { - case 1: - asm volatile("inb %%dx, %%al" ::"d"(value) : "%eax"); - break; - case 2: - asm volatile("inw %%dx, %%ax" ::"d"(value) : "%eax"); - break; - case 4: - asm volatile("inl %%dx, %%eax" ::"d"(value) : "%eax"); - break; - default: - FAIL() << "invalid input width, only 1, 2 or 4 is allowed"; - } - }, - ::testing::KilledBySignal(SIGSEGV), ""); -} -#elif defined(__aarch64__) -void inline Halt() { asm("hlt #0\r\n"); } -#endif - -TEST(ExceptionTest, Halt) { - // In order to prevent the regular handler from messing with things (and - // perhaps refaulting until some other signal occurs), we reset the handler to - // the default action here and ensure that it dies correctly. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); - -#if defined(__x86_64__) - EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), ""); -#elif defined(__aarch64__) - EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGILL), ""); -#endif -} - -#if defined(__x86_64__) -TEST(ExceptionTest, DivideByZero) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); - - EXPECT_EXIT( - { - uint32_t remainder; - uint32_t quotient; - uint32_t divisor = 0; - uint64_t value = 1; - asm("divl 0(%2)\r\n" - : "=d"(remainder), "=a"(quotient) - : "r"(&divisor), "d"(value >> 32), "a"(value)); - TEST_CHECK(quotient > 0); // Force dependency. - }, - ::testing::KilledBySignal(SIGFPE), ""); -} - -// By default, x87 exceptions are masked and simply return a default value. -TEST(ExceptionTest, X87DivideByZeroMasked) { - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm("fildl %[value]\r\n" - "fidivl %[divisor]\r\n" - "fistpl %[quotient]\r\n" - : [ quotient ] "=m"(quotient) - : [ value ] "m"(value), [ divisor ] "m"(divisor)); - - EXPECT_EQ(quotient, INT32_MIN); -} - -// When unmasked, division by zero raises SIGFPE. -TEST(ExceptionTest, X87DivideByZeroUnmasked) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); - - EXPECT_EXIT( - { - // Clear the divide by zero exception mask. - constexpr uint16_t kControlWord = - kX87ControlWordDefault & ~kX87ControlWordDiv0Mask; - - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm volatile( - "fldcw %[cw]\r\n" - "fildl %[value]\r\n" - "fidivl %[divisor]\r\n" - "fistpl %[quotient]\r\n" - : [ quotient ] "=m"(quotient) - : [ cw ] "m"(kControlWord), [ value ] "m"(value), - [ divisor ] "m"(divisor)); - }, - ::testing::KilledBySignal(SIGFPE), ""); -} - -// Pending exceptions in the x87 status register are not clobbered by syscalls. -TEST(ExceptionTest, X87StatusClobber) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); - - EXPECT_EXIT( - { - // Clear the divide by zero exception mask. - constexpr uint16_t kControlWord = - kX87ControlWordDefault & ~kX87ControlWordDiv0Mask; - - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm volatile( - "fildl %[value]\r\n" - "fidivl %[divisor]\r\n" - // Exception is masked, so it does not occur here. - "fistpl %[quotient]\r\n" - - // SYS_getpid placed in rax by constraint. - "syscall\r\n" - - // Unmask exception. The syscall didn't clobber the pending - // exception, so now it can be raised. - // - // N.B. "a floating-point exception will be generated upon execution - // of the *next* floating-point instruction". - "fldcw %[cw]\r\n" - "fwait\r\n" - : [ quotient ] "=m"(quotient) - : [ value ] "m"(value), [ divisor ] "m"(divisor), "a"(SYS_getpid), - [ cw ] "m"(kControlWord) - : "rcx", "r11"); - }, - ::testing::KilledBySignal(SIGFPE), ""); -} - -// By default, SSE exceptions are masked and simply return a default value. -TEST(ExceptionTest, SSEDivideByZeroMasked) { - uint32_t status; - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm("cvtsi2ssl %[value], %%xmm0\r\n" - "cvtsi2ssl %[divisor], %%xmm1\r\n" - "divss %%xmm1, %%xmm0\r\n" - "cvtss2sil %%xmm0, %[quotient]\r\n" - : [ quotient ] "=r"(quotient), [ status ] "=r"(status) - : [ value ] "r"(value), [ divisor ] "r"(divisor) - : "xmm0", "xmm1"); - - EXPECT_EQ(quotient, INT32_MIN); -} - -// When unmasked, division by zero raises SIGFPE. -TEST(ExceptionTest, SSEDivideByZeroUnmasked) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa)); - - EXPECT_EXIT( - { - // Clear the divide by zero exception mask. - constexpr uint32_t kMXCSR = kMXCSRDefault & ~kMXCSRDiv0Mask; - - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm volatile( - "ldmxcsr %[mxcsr]\r\n" - "cvtsi2ssl %[value], %%xmm0\r\n" - "cvtsi2ssl %[divisor], %%xmm1\r\n" - "divss %%xmm1, %%xmm0\r\n" - "cvtss2sil %%xmm0, %[quotient]\r\n" - : [ quotient ] "=r"(quotient) - : [ mxcsr ] "m"(kMXCSR), [ value ] "r"(value), - [ divisor ] "r"(divisor) - : "xmm0", "xmm1"); - }, - ::testing::KilledBySignal(SIGFPE), ""); -} - -// Pending exceptions in the SSE status register are not clobbered by syscalls. -TEST(ExceptionTest, SSEStatusClobber) { - uint32_t mxcsr; - int32_t quotient; - int32_t value = 1; - int32_t divisor = 0; - asm("cvtsi2ssl %[value], %%xmm0\r\n" - "cvtsi2ssl %[divisor], %%xmm1\r\n" - "divss %%xmm1, %%xmm0\r\n" - // Exception is masked, so it does not occur here. - "cvtss2sil %%xmm0, %[quotient]\r\n" - - // SYS_getpid placed in rax by constraint. - "syscall\r\n" - - // Intel SDM Vol 1, Ch 10.2.3.1 "SIMD Floating-Point Mask and Flag Bits": - // "If LDMXCSR or FXRSTOR clears a mask bit and sets the corresponding - // exception flag bit, a SIMD floating-point exception will not be - // generated as a result of this change. The unmasked exception will be - // generated only upon the execution of the next SSE/SSE2/SSE3 instruction - // that detects the unmasked exception condition." - // - // Though ambiguous, empirical evidence indicates that this means that - // exception flags set in the status register will never cause an - // exception to be raised; only a new exception condition will do so. - // - // Thus here we just check for the flag itself rather than trying to raise - // the exception. - "stmxcsr %[mxcsr]\r\n" - : [ quotient ] "=r"(quotient), [ mxcsr ] "+m"(mxcsr) - : [ value ] "r"(value), [ divisor ] "r"(divisor), "a"(SYS_getpid) - : "xmm0", "xmm1", "rcx", "r11"); - - EXPECT_TRUE(mxcsr & kMXCSRDiv0Flag); -} - -TEST(ExceptionTest, IOAccessFault) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); - - InIOHelper(1, 0x0); - InIOHelper(2, 0x7); - InIOHelper(4, 0x6); - InIOHelper(1, 0xffff); - InIOHelper(2, 0xffff); - InIOHelper(4, 0xfffd); -} - -TEST(ExceptionTest, Alignment) { - SetAlignmentCheck(); - ClearAlignmentCheck(); -} - -TEST(ExceptionTest, AlignmentHalt) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); - - // Reported upstream. We need to ensure that bad flags are cleared even in - // fault paths. Set the alignment flag and then generate an exception. - EXPECT_EXIT( - { - SetAlignmentCheck(); - Halt(); - }, - ::testing::KilledBySignal(SIGSEGV), ""); -} - -TEST(ExceptionTest, AlignmentCheck) { - SKIP_IF(PlatformSupportAlignmentCheck() != PlatformSupport::Allowed); - - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGBUS, sa)); - - EXPECT_EXIT( - { - char array[16]; - SetAlignmentCheck(); - for (int i = 0; i < 8; i++) { - // At least 7/8 offsets will be unaligned here. - uint64_t* ptr = reinterpret_cast<uint64_t*>(&array[i]); - asm("mov %0, 0(%0)\r\n" : : "r"(ptr) : "ax"); - } - }, - ::testing::KilledBySignal(SIGBUS), ""); -} - -TEST(ExceptionTest, Int3Normal) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa)); - - EXPECT_EXIT(Int3Normal(), ::testing::KilledBySignal(SIGTRAP), ""); -} - -TEST(ExceptionTest, Int3Compact) { - // See above. - struct sigaction sa = {}; - sa.sa_handler = SIG_DFL; - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa)); - - EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), ""); -} -#endif - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc deleted file mode 100644 index c5acfc794..000000000 --- a/test/syscalls/linux/exec.cc +++ /dev/null @@ -1,904 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/exec.h" - -#include <errno.h> -#include <fcntl.h> -#include <sys/eventfd.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <unistd.h> - -#include <iostream> -#include <memory> -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "absl/synchronization/mutex.h" -#include "absl/types/optional.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr char kBasicWorkload[] = "test/syscalls/linux/exec_basic_workload"; -constexpr char kExitScript[] = "test/syscalls/linux/exit_script"; -constexpr char kStateWorkload[] = "test/syscalls/linux/exec_state_workload"; -constexpr char kProcExeWorkload[] = - "test/syscalls/linux/exec_proc_exe_workload"; -constexpr char kAssertClosedWorkload[] = - "test/syscalls/linux/exec_assert_closed_workload"; -constexpr char kPriorityWorkload[] = "test/syscalls/linux/priority_execve"; - -constexpr char kExit42[] = "--exec_exit_42"; -constexpr char kExecWithThread[] = "--exec_exec_with_thread"; -constexpr char kExecFromThread[] = "--exec_exec_from_thread"; - -// Runs file specified by dirfd and pathname with argv and checks that the exit -// status is expect_status and that stderr contains expect_stderr. -void CheckExecHelper(const absl::optional<int32_t> dirfd, - const std::string& pathname, const ExecveArray& argv, - const ExecveArray& envv, const int flags, - int expect_status, const std::string& expect_stderr) { - int pipe_fds[2]; - ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds()); - - FileDescriptor read_fd(pipe_fds[0]); - FileDescriptor write_fd(pipe_fds[1]); - - pid_t child; - int execve_errno; - - const auto remap_stderr = [pipe_fds] { - // Remap stdin and stdout to /dev/null. - int fd = open("/dev/null", O_RDWR | O_CLOEXEC); - if (fd < 0) { - _exit(errno); - } - - int ret = dup2(fd, 0); - if (ret < 0) { - _exit(errno); - } - - ret = dup2(fd, 1); - if (ret < 0) { - _exit(errno); - } - - // And stderr to the pipe. - ret = dup2(pipe_fds[1], 2); - if (ret < 0) { - _exit(errno); - } - - // Here, we'd ideally close all other FDs inherited from the parent. - // However, that's not worth the effort and CloexecNormalFile and - // CloexecEventfd depend on that not happening. - }; - - Cleanup kill; - if (dirfd.has_value()) { - kill = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(*dirfd, pathname, argv, - envv, flags, remap_stderr, - &child, &execve_errno)); - } else { - kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(pathname, argv, envv, remap_stderr, &child, &execve_errno)); - } - - ASSERT_EQ(0, execve_errno); - - // Not needed anymore. - write_fd.reset(); - - // Read stderr until the child exits. - std::string output; - constexpr int kSize = 128; - char buf[kSize]; - int n; - do { - ASSERT_THAT(n = ReadFd(read_fd.get(), buf, kSize), SyscallSucceeds()); - if (n > 0) { - output.append(buf, n); - } - } while (n > 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_EQ(status, expect_status); - - // Process cleanup no longer needed. - kill.Release(); - - EXPECT_TRUE(absl::StrContains(output, expect_stderr)) << output; -} - -void CheckExec(const std::string& filename, const ExecveArray& argv, - const ExecveArray& envv, int expect_status, - const std::string& expect_stderr) { - CheckExecHelper(/*dirfd=*/absl::optional<int32_t>(), filename, argv, envv, - /*flags=*/0, expect_status, expect_stderr); -} - -void CheckExecveat(const int32_t dirfd, const std::string& pathname, - const ExecveArray& argv, const ExecveArray& envv, - const int flags, int expect_status, - const std::string& expect_stderr) { - CheckExecHelper(absl::optional<int32_t>(dirfd), pathname, argv, envv, flags, - expect_status, expect_stderr); -} - -TEST(ExecTest, EmptyPath) { - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec("", {}, {}, nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ENOENT); -} - -TEST(ExecTest, Basic) { - CheckExec(RunfilePath(kBasicWorkload), {RunfilePath(kBasicWorkload)}, {}, - ArgEnvExitStatus(0, 0), - absl::StrCat(RunfilePath(kBasicWorkload), "\n")); -} - -TEST(ExecTest, OneArg) { - CheckExec(RunfilePath(kBasicWorkload), {RunfilePath(kBasicWorkload), "1"}, {}, - ArgEnvExitStatus(1, 0), - absl::StrCat(RunfilePath(kBasicWorkload), "\n1\n")); -} - -TEST(ExecTest, FiveArg) { - CheckExec(RunfilePath(kBasicWorkload), - {RunfilePath(kBasicWorkload), "1", "2", "3", "4", "5"}, {}, - ArgEnvExitStatus(5, 0), - absl::StrCat(RunfilePath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); -} - -TEST(ExecTest, OneEnv) { - CheckExec(RunfilePath(kBasicWorkload), {RunfilePath(kBasicWorkload)}, {"1"}, - ArgEnvExitStatus(0, 1), - absl::StrCat(RunfilePath(kBasicWorkload), "\n1\n")); -} - -TEST(ExecTest, FiveEnv) { - CheckExec(RunfilePath(kBasicWorkload), {RunfilePath(kBasicWorkload)}, - {"1", "2", "3", "4", "5"}, ArgEnvExitStatus(0, 5), - absl::StrCat(RunfilePath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); -} - -TEST(ExecTest, OneArgOneEnv) { - CheckExec(RunfilePath(kBasicWorkload), {RunfilePath(kBasicWorkload), "arg"}, - {"env"}, ArgEnvExitStatus(1, 1), - absl::StrCat(RunfilePath(kBasicWorkload), "\narg\nenv\n")); -} - -TEST(ExecTest, InterpreterScript) { - CheckExec(RunfilePath(kExitScript), {RunfilePath(kExitScript), "25"}, {}, - ArgEnvExitStatus(25, 0), ""); -} - -// Everything after the path in the interpreter script is a single argument. -TEST(ExecTest, InterpreterScriptArgSplit) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo bar"), - 0755)); - - CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo bar\n", script.path(), "\n")); -} - -// Original argv[0] is replaced with the script path. -TEST(ExecTest, InterpreterScriptArgvZero) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755)); - - CheckExec(script.path(), {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); -} - -// Original argv[0] is replaced with the script path, exactly as passed to -// execve. -TEST(ExecTest, InterpreterScriptArgvZeroRelative) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755)); - - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto script_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path())); - - CheckExec(script_relative, {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script_relative, "\n")); -} - -// argv[0] is added as the script path, even if there was none. -TEST(ExecTest, InterpreterScriptArgvZeroAdded) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755)); - - CheckExec(script.path(), {}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); -} - -// A NUL byte in the script line ends parsing. -TEST(ExecTest, InterpreterScriptArgNUL) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), - absl::StrCat("#!", link.path(), " foo", std::string(1, '\0'), "bar"), - 0755)); - - CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); -} - -// Trailing whitespace following interpreter path is ignored. -TEST(ExecTest, InterpreterScriptTrailingWhitespace) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " "), 0755)); - - CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); -} - -// Multiple whitespace characters between interpreter and arg allowed. -TEST(ExecTest, InterpreterScriptArgWhitespace) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo"), 0755)); - - CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); -} - -TEST(ExecTest, InterpreterScriptNoPath) { - TempPath script = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!", 0755)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ENOEXEC); -} - -// AT_EXECFN is the path passed to execve. -TEST(ExecTest, ExecFn) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kStateWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " PrintExecFn"), - 0755)); - - // Pass the script as a relative path and assert that is what appears in - // AT_EXECFN. - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto script_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path())); - - CheckExec(script_relative, {script_relative}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(script_relative, "\n")); -} - -TEST(ExecTest, ExecName) { - std::string path = RunfilePath(kStateWorkload); - - CheckExec(path, {path, "PrintExecName"}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(Basename(path).substr(0, 15), "\n")); -} - -TEST(ExecTest, ExecNameScript) { - // Symlink through /tmp to ensure the path is short enough. - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kStateWorkload))); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), - absl::StrCat("#!", link.path(), " PrintExecName"), 0755)); - - std::string script_path = script.path(); - - CheckExec(script_path, {script_path}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(Basename(script_path).substr(0, 15), "\n")); -} - -// execve may be called by a multithreaded process. -TEST(ExecTest, WithSiblingThread) { - CheckExec("/proc/self/exe", {"/proc/self/exe", kExecWithThread}, {}, - W_EXITCODE(42, 0), ""); -} - -// execve may be called from a thread other than the leader of a multithreaded -// process. -TEST(ExecTest, FromSiblingThread) { - CheckExec("/proc/self/exe", {"/proc/self/exe", kExecFromThread}, {}, - W_EXITCODE(42, 0), ""); -} - -TEST(ExecTest, NotFound) { - char* const argv[] = {nullptr}; - char* const envp[] = {nullptr}; - EXPECT_THAT(execve("/file/does/not/exist", argv, envp), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(ExecTest, NoExecPerm) { - char* const argv[] = {nullptr}; - char* const envp[] = {nullptr}; - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - EXPECT_THAT(execve(f.path().c_str(), argv, envp), - SyscallFailsWithErrno(EACCES)); -} - -// A signal handler we never expect to be called. -void SignalHandler(int signo) { - std::cerr << "Signal " << signo << " raised." << std::endl; - exit(1); -} - -// Signal handlers are reset on execve(2), unless they have default or ignored -// disposition. -TEST(ExecStateTest, HandlerReset) { - struct sigaction sa; - sa.sa_handler = SignalHandler; - ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); - - ExecveArray args = { - RunfilePath(kStateWorkload), - "CheckSigHandler", - absl::StrCat(SIGUSR1), - absl::StrCat(absl::Hex(reinterpret_cast<uintptr_t>(SIG_DFL))), - }; - - CheckExec(RunfilePath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); -} - -// Ignored signal dispositions are not reset. -TEST(ExecStateTest, IgnorePreserved) { - struct sigaction sa; - sa.sa_handler = SIG_IGN; - ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); - - ExecveArray args = { - RunfilePath(kStateWorkload), - "CheckSigHandler", - absl::StrCat(SIGUSR1), - absl::StrCat(absl::Hex(reinterpret_cast<uintptr_t>(SIG_IGN))), - }; - - CheckExec(RunfilePath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); -} - -// Signal masks are not reset on exec -TEST(ExecStateTest, SignalMask) { - sigset_t s; - sigemptyset(&s); - sigaddset(&s, SIGUSR1); - ASSERT_THAT(sigprocmask(SIG_BLOCK, &s, nullptr), SyscallSucceeds()); - - ExecveArray args = { - RunfilePath(kStateWorkload), - "CheckSigBlocked", - absl::StrCat(SIGUSR1), - }; - - CheckExec(RunfilePath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); -} - -// itimers persist across execve. -// N.B. Timers created with timer_create(2) should not be preserved! -TEST(ExecStateTest, ItimerPreserved) { - // The fork in ForkAndExec clears itimers, so only set them up after fork. - auto setup_itimer = [] { - // Ignore SIGALRM, as we don't actually care about timer - // expirations. - struct sigaction sa; - sa.sa_handler = SIG_IGN; - int ret = sigaction(SIGALRM, &sa, nullptr); - if (ret < 0) { - _exit(errno); - } - - struct itimerval itv; - itv.it_interval.tv_sec = 1; - itv.it_interval.tv_usec = 0; - itv.it_value.tv_sec = 1; - itv.it_value.tv_usec = 0; - ret = setitimer(ITIMER_REAL, &itv, nullptr); - if (ret < 0) { - _exit(errno); - } - }; - - std::string filename = RunfilePath(kStateWorkload); - ExecveArray argv = { - filename, - "CheckItimerEnabled", - absl::StrCat(ITIMER_REAL), - }; - - pid_t child; - int execve_errno; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(filename, argv, {}, setup_itimer, &child, &execve_errno)); - ASSERT_EQ(0, execve_errno); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_EQ(0, status); - - // Process cleanup no longer needed. - kill.Release(); -} - -TEST(ProcSelfExe, ChangesAcrossExecve) { - // See exec_proc_exe_workload for more details. We simply - // assert that the /proc/self/exe link changes across execve. - CheckExec(RunfilePath(kProcExeWorkload), - {RunfilePath(kProcExeWorkload), - ASSERT_NO_ERRNO_AND_VALUE(ProcessExePath(getpid()))}, - {}, W_EXITCODE(0, 0), ""); -} - -TEST(ExecTest, CloexecNormalFile) { - TempPath tempFile = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "bar", 0755)); - const FileDescriptor fd_closed_on_exec = - ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY | O_CLOEXEC)); - - CheckExec(RunfilePath(kAssertClosedWorkload), - {RunfilePath(kAssertClosedWorkload), - absl::StrCat(fd_closed_on_exec.get())}, - {}, W_EXITCODE(0, 0), ""); - - // The assert closed workload exits with code 2 if the file still exists. We - // can use this to do a negative test. - const FileDescriptor fd_open_on_exec = - ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY)); - - CheckExec( - RunfilePath(kAssertClosedWorkload), - {RunfilePath(kAssertClosedWorkload), absl::StrCat(fd_open_on_exec.get())}, - {}, W_EXITCODE(2, 0), ""); -} - -TEST(ExecTest, CloexecEventfd) { - int efd; - ASSERT_THAT(efd = eventfd(0, EFD_CLOEXEC), SyscallSucceeds()); - FileDescriptor fd(efd); - - CheckExec(RunfilePath(kAssertClosedWorkload), - {RunfilePath(kAssertClosedWorkload), absl::StrCat(fd.get())}, {}, - W_EXITCODE(0, 0), ""); -} - -constexpr int kLinuxMaxSymlinks = 40; - -TEST(ExecTest, SymlinkLimitExceeded) { - std::string path = RunfilePath(kBasicWorkload); - - // Hold onto TempPath objects so they are not destructed prematurely. - std::vector<TempPath> symlinks; - for (int i = 0; i < kLinuxMaxSymlinks + 1; i++) { - symlinks.push_back( - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateSymlinkTo("/tmp", path))); - path = symlinks[i].path(); - } - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(path, {path}, {}, /*child=*/nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -TEST(ExecTest, SymlinkLimitRefreshedForInterpreter) { - std::string tmp_dir = "/tmp"; - std::string interpreter_path = "/bin/echo"; - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - tmp_dir, absl::StrCat("#!", interpreter_path), 0755)); - std::string script_path = script.path(); - - // Hold onto TempPath objects so they are not destructed prematurely. - std::vector<TempPath> interpreter_symlinks; - std::vector<TempPath> script_symlinks; - // Replace both the interpreter and script paths with symlink chains of just - // over half the symlink limit each; this is the minimum required to test that - // the symlink limit applies separately to each traversal, while tolerating - // some symlinks in the resolution of (the original) interpreter_path and - // script_path. - for (int i = 0; i < (kLinuxMaxSymlinks / 2) + 1; i++) { - interpreter_symlinks.push_back(ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(tmp_dir, interpreter_path))); - interpreter_path = interpreter_symlinks[i].path(); - script_symlinks.push_back(ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(tmp_dir, script_path))); - script_path = script_symlinks[i].path(); - } - - CheckExec(script_path, {script_path}, {}, ArgEnvExitStatus(0, 0), ""); -} - -TEST(ExecveatTest, BasicWithFDCWD) { - std::string path = RunfilePath(kBasicWorkload); - CheckExecveat(AT_FDCWD, path, {path}, {}, /*flags=*/0, ArgEnvExitStatus(0, 0), - absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, Basic) { - std::string absolute_path = RunfilePath(kBasicWorkload); - std::string parent_dir = std::string(Dirname(absolute_path)); - std::string base = std::string(Basename(absolute_path)); - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY)); - - CheckExecveat(dirfd.get(), base, {absolute_path}, {}, /*flags=*/0, - ArgEnvExitStatus(0, 0), absl::StrCat(absolute_path, "\n")); -} - -TEST(ExecveatTest, FDNotADirectory) { - std::string absolute_path = RunfilePath(kBasicWorkload); - std::string base = std::string(Basename(absolute_path)); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(absolute_path, 0)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(fd.get(), base, {absolute_path}, {}, - /*flags=*/0, /*child=*/nullptr, - &execve_errno)); - EXPECT_EQ(execve_errno, ENOTDIR); -} - -TEST(ExecveatTest, AbsolutePathWithFDCWD) { - std::string path = RunfilePath(kBasicWorkload); - CheckExecveat(AT_FDCWD, path, {path}, {}, ArgEnvExitStatus(0, 0), 0, - absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, AbsolutePath) { - std::string path = RunfilePath(kBasicWorkload); - // File descriptor should be ignored when an absolute path is given. - const int32_t badFD = -1; - CheckExecveat(badFD, path, {path}, {}, ArgEnvExitStatus(0, 0), 0, - absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, EmptyPathBasic) { - std::string path = RunfilePath(kBasicWorkload); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_PATH)); - - CheckExecveat(fd.get(), "", {path}, {}, AT_EMPTY_PATH, ArgEnvExitStatus(0, 0), - absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, EmptyPathWithDirFD) { - std::string path = RunfilePath(kBasicWorkload); - std::string parent_dir = std::string(Dirname(path)); - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(dirfd.get(), "", {path}, {}, - AT_EMPTY_PATH, - /*child=*/nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -TEST(ExecveatTest, EmptyPathWithoutEmptyPathFlag) { - std::string path = RunfilePath(kBasicWorkload); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_PATH)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat( - fd.get(), "", {path}, {}, /*flags=*/0, /*child=*/nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ENOENT); -} - -TEST(ExecveatTest, AbsolutePathWithEmptyPathFlag) { - std::string path = RunfilePath(kBasicWorkload); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_PATH)); - - CheckExecveat(fd.get(), path, {path}, {}, AT_EMPTY_PATH, - ArgEnvExitStatus(0, 0), absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, RelativePathWithEmptyPathFlag) { - std::string absolute_path = RunfilePath(kBasicWorkload); - std::string parent_dir = std::string(Dirname(absolute_path)); - std::string base = std::string(Basename(absolute_path)); - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY)); - - CheckExecveat(dirfd.get(), base, {absolute_path}, {}, AT_EMPTY_PATH, - ArgEnvExitStatus(0, 0), absl::StrCat(absolute_path, "\n")); -} - -TEST(ExecveatTest, SymlinkNoFollowWithRelativePath) { - std::string parent_dir = "/tmp"; - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(parent_dir, RunfilePath(kBasicWorkload))); - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY)); - std::string base = std::string(Basename(link.path())); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(dirfd.get(), base, {base}, {}, - AT_SYMLINK_NOFOLLOW, - /*child=*/nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -TEST(ExecveatTest, UnshareFiles) { - TempPath tempFile = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "bar", 0755)); - const FileDescriptor fd_closed_on_exec = - ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY | O_CLOEXEC)); - - ExecveArray argv = {"test"}; - ExecveArray envp; - std::string child_path = RunfilePath(kBasicWorkload); - pid_t child = - syscall(__NR_clone, SIGCHLD | CLONE_VFORK | CLONE_FILES, 0, 0, 0, 0); - if (child == 0) { - execve(child_path.c_str(), argv.get(), envp.get()); - _exit(1); - } - ASSERT_THAT(child, SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_EQ(status, 0); - - struct stat st; - EXPECT_THAT(fstat(fd_closed_on_exec.get(), &st), SyscallSucceeds()); -} - -TEST(ExecveatTest, SymlinkNoFollowWithAbsolutePath) { - std::string parent_dir = "/tmp"; - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(parent_dir, RunfilePath(kBasicWorkload))); - std::string path = link.path(); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(AT_FDCWD, path, {path}, {}, - AT_SYMLINK_NOFOLLOW, - /*child=*/nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -TEST(ExecveatTest, SymlinkNoFollowAndEmptyPath) { - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload))); - std::string path = link.path(); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, 0)); - - CheckExecveat(fd.get(), "", {path}, {}, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, - ArgEnvExitStatus(0, 0), absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, SymlinkNoFollowIgnoreSymlinkAncestor) { - TempPath parent_link = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateSymlinkTo("/tmp", "/bin")); - std::string path_with_symlink = JoinPath(parent_link.path(), "echo"); - - CheckExecveat(AT_FDCWD, path_with_symlink, {path_with_symlink}, {}, - AT_SYMLINK_NOFOLLOW, ArgEnvExitStatus(0, 0), ""); -} - -TEST(ExecveatTest, SymlinkNoFollowWithNormalFile) { - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/bin", O_DIRECTORY)); - - CheckExecveat(dirfd.get(), "echo", {"echo"}, {}, AT_SYMLINK_NOFOLLOW, - ArgEnvExitStatus(0, 0), ""); -} - -TEST(ExecveatTest, BasicWithCloexecFD) { - std::string path = RunfilePath(kBasicWorkload); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CLOEXEC)); - - CheckExecveat(fd.get(), "", {path}, {}, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH, - ArgEnvExitStatus(0, 0), absl::StrCat(path, "\n")); -} - -TEST(ExecveatTest, InterpreterScriptWithCloexecFD) { - std::string path = RunfilePath(kExitScript); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CLOEXEC)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(fd.get(), "", {path}, {}, - AT_EMPTY_PATH, /*child=*/nullptr, - &execve_errno)); - EXPECT_EQ(execve_errno, ENOENT); -} - -TEST(ExecveatTest, InterpreterScriptWithCloexecDirFD) { - std::string absolute_path = RunfilePath(kExitScript); - std::string parent_dir = std::string(Dirname(absolute_path)); - std::string base = std::string(Basename(absolute_path)); - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_CLOEXEC | O_DIRECTORY)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(dirfd.get(), base, {base}, {}, - /*flags=*/0, /*child=*/nullptr, - &execve_errno)); - EXPECT_EQ(execve_errno, ENOENT); -} - -TEST(ExecveatTest, InvalidFlags) { - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat( - /*dirfd=*/-1, "", {}, {}, /*flags=*/0xFFFF, /*child=*/nullptr, - &execve_errno)); - EXPECT_EQ(execve_errno, EINVAL); -} - -// Priority consistent across calls to execve() -TEST(GetpriorityTest, ExecveMaintainsPriority) { - int prio = 16; - ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), prio), SyscallSucceeds()); - - // To avoid trying to use negative exit values, check for - // 20 - prio. Since prio should always be in the range [-20, 19], - // this leave expected_exit_code in the range [1, 40]. - int expected_exit_code = 20 - prio; - - // Program run (priority_execve) will exit(X) where - // X=getpriority(PRIO_PROCESS,0). Check that this exit value is prio. - CheckExec(RunfilePath(kPriorityWorkload), {RunfilePath(kPriorityWorkload)}, - {}, W_EXITCODE(expected_exit_code, 0), ""); -} - -void ExecWithThread() { - // Used to ensure that the thread has actually started. - absl::Mutex mu; - bool started = false; - - ScopedThread t([&] { - mu.Lock(); - started = true; - mu.Unlock(); - - while (true) { - pause(); - } - }); - - mu.LockWhen(absl::Condition(&started)); - mu.Unlock(); - - const ExecveArray argv = {"/proc/self/exe", kExit42}; - const ExecveArray envv; - - execve("/proc/self/exe", argv.get(), envv.get()); - exit(errno); -} - -void ExecFromThread() { - ScopedThread t([] { - const ExecveArray argv = {"/proc/self/exe", kExit42}; - const ExecveArray envv; - - execve("/proc/self/exe", argv.get(), envv.get()); - exit(errno); - }); - - while (true) { - pause(); - } -} - -bool ValidateProcCmdlineVsArgv(const int argc, const char* const* argv) { - auto contents_or = GetContents("/proc/self/cmdline"); - if (!contents_or.ok()) { - std::cerr << "Unable to get /proc/self/cmdline: " << contents_or.error() - << std::endl; - return false; - } - auto contents = contents_or.ValueOrDie(); - if (contents.back() != '\0') { - std::cerr << "Non-null terminated /proc/self/cmdline!" << std::endl; - return false; - } - contents.pop_back(); - std::vector<std::string> procfs_cmdline = absl::StrSplit(contents, '\0'); - - if (static_cast<int>(procfs_cmdline.size()) != argc) { - std::cerr << "argc = " << argc << " != " << procfs_cmdline.size() - << std::endl; - return false; - } - - for (int i = 0; i < argc; ++i) { - if (procfs_cmdline[i] != argv[i]) { - std::cerr << "Procfs command line argument " << i << " mismatch " - << procfs_cmdline[i] << " != " << argv[i] << std::endl; - return false; - } - } - return true; -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // Start by validating that the stack argv is consistent with procfs. - if (!gvisor::testing::ValidateProcCmdlineVsArgv(argc, argv)) { - return 1; - } - - // Some of these tests require no background threads, so check for them before - // TestInit. - for (int i = 0; i < argc; i++) { - absl::string_view arg(argv[i]); - - if (arg == gvisor::testing::kExit42) { - return 42; - } - if (arg == gvisor::testing::kExecWithThread) { - gvisor::testing::ExecWithThread(); - return 1; - } - if (arg == gvisor::testing::kExecFromThread) { - gvisor::testing::ExecFromThread(); - return 1; - } - } - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/exec.h b/test/syscalls/linux/exec.h deleted file mode 100644 index 5c0f7e654..000000000 --- a/test/syscalls/linux/exec.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_EXEC_H_ -#define GVISOR_TEST_SYSCALLS_EXEC_H_ - -#include <sys/wait.h> - -namespace gvisor { -namespace testing { - -// Returns the exit code used by exec_basic_workload. -inline int ArgEnvExitCode(int args, int envs) { return args + envs * 10; } - -// Returns the exit status used by exec_basic_workload. -inline int ArgEnvExitStatus(int args, int envs) { - return W_EXITCODE(ArgEnvExitCode(args, envs), 0); -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_EXEC_H_ diff --git a/test/syscalls/linux/exec_assert_closed_workload.cc b/test/syscalls/linux/exec_assert_closed_workload.cc deleted file mode 100644 index 95643618d..000000000 --- a/test/syscalls/linux/exec_assert_closed_workload.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <iostream> - -#include "absl/strings/numbers.h" - -int main(int argc, char** argv) { - if (argc != 2) { - std::cerr << "need two arguments, got " << argc; - exit(1); - } - int fd; - if (!absl::SimpleAtoi(argv[1], &fd)) { - std::cerr << "fd: " << argv[1] << " could not be parsed" << std::endl; - exit(1); - } - struct stat s; - if (fstat(fd, &s) == 0) { - std::cerr << "fd: " << argv[1] << " should not be valid" << std::endl; - exit(2); - } - if (errno != EBADF) { - std::cerr << "fstat fd: " << argv[1] << " got errno: " << errno - << " wanted: " << EBADF << std::endl; - exit(1); - } - return 0; -} diff --git a/test/syscalls/linux/exec_basic_workload.cc b/test/syscalls/linux/exec_basic_workload.cc deleted file mode 100644 index 1bbd6437e..000000000 --- a/test/syscalls/linux/exec_basic_workload.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdlib.h> - -#include <iostream> - -#include "test/syscalls/linux/exec.h" - -int main(int argc, char** argv, char** envp) { - int i; - for (i = 0; i < argc; i++) { - std::cerr << argv[i] << std::endl; - } - for (i = 0; envp[i] != nullptr; i++) { - std::cerr << envp[i] << std::endl; - } - exit(gvisor::testing::ArgEnvExitCode(argc - 1, i)); - return 0; -} diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc deleted file mode 100644 index b0fb120c6..000000000 --- a/test/syscalls/linux/exec_binary.cc +++ /dev/null @@ -1,1681 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <elf.h> -#include <errno.h> -#include <signal.h> -#include <sys/ptrace.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/user.h> -#include <unistd.h> - -#include <algorithm> -#include <functional> -#include <iterator> -#include <tuple> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -#if !defined(__x86_64__) && !defined(__aarch64__) -// The assembly stub and ELF internal details must be ported to other arches. -#error "Test only supported on x86-64/arm64" -#endif // __x86_64__ || __aarch64__ - -#if defined(__x86_64__) -#define EM_TYPE EM_X86_64 -#define IP_REG(p) ((p).rip) -#define RAX_REG(p) ((p).rax) -#define RDI_REG(p) ((p).rdi) -#define RETURN_REG(p) ((p).rax) - -// amd64 stub that calls PTRACE_TRACEME and sends itself SIGSTOP. -const char kPtraceCode[] = { - // movq $101, %rax /* ptrace */ - '\x48', - '\xc7', - '\xc0', - '\x65', - '\x00', - '\x00', - '\x00', - // movq $0, %rsi /* PTRACE_TRACEME */ - '\x48', - '\xc7', - '\xc6', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %rdi - '\x48', - '\xc7', - '\xc7', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %rdx - '\x48', - '\xc7', - '\xc2', - '\x00', - '\x00', - '\x00', - '\x00', - // movq $0, %r10 - '\x49', - '\xc7', - '\xc2', - '\x00', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', - - // movq $39, %rax /* getpid */ - '\x48', - '\xc7', - '\xc0', - '\x27', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', - - // movq %rax, %rdi /* pid */ - '\x48', - '\x89', - '\xc7', - // movq $62, %rax /* kill */ - '\x48', - '\xc7', - '\xc0', - '\x3e', - '\x00', - '\x00', - '\x00', - // movq $19, %rsi /* SIGSTOP */ - '\x48', - '\xc7', - '\xc6', - '\x13', - '\x00', - '\x00', - '\x00', - // syscall - '\x0f', - '\x05', -}; - -// Size of a syscall instruction. -constexpr int kSyscallSize = 2; - -#elif defined(__aarch64__) -#define EM_TYPE EM_AARCH64 -#define IP_REG(p) ((p).pc) -#define RAX_REG(p) ((p).regs[8]) -#define RDI_REG(p) ((p).regs[0]) -#define RETURN_REG(p) ((p).regs[0]) - -const char kPtraceCode[] = { - // MOVD $117, R8 /* ptrace */ - '\xa8', - '\x0e', - '\x80', - '\xd2', - // MOVD $0, R0 /* PTRACE_TRACEME */ - '\x00', - '\x00', - '\x80', - '\xd2', - // MOVD $0, R1 /* pid */ - '\x01', - '\x00', - '\x80', - '\xd2', - // MOVD $0, R2 /* addr */ - '\x02', - '\x00', - '\x80', - '\xd2', - // MOVD $0, R3 /* data */ - '\x03', - '\x00', - '\x80', - '\xd2', - // SVC - '\x01', - '\x00', - '\x00', - '\xd4', - // MOVD $172, R8 /* getpid */ - '\x88', - '\x15', - '\x80', - '\xd2', - // SVC - '\x01', - '\x00', - '\x00', - '\xd4', - // MOVD $129, R8 /* kill, R0=pid */ - '\x28', - '\x10', - '\x80', - '\xd2', - // MOVD $19, R1 /* SIGSTOP */ - '\x61', - '\x02', - '\x80', - '\xd2', - // SVC - '\x01', - '\x00', - '\x00', - '\xd4', -}; -// Size of a syscall instruction. -constexpr int kSyscallSize = 4; -#else -#error "Unknown architecture" -#endif - -// This test suite tests executable loading in the kernel (ELF and interpreter -// scripts). - -// Parameterized ELF types for 64 and 32 bit. -template <int Size> -struct ElfTypes; - -template <> -struct ElfTypes<64> { - typedef Elf64_Ehdr ElfEhdr; - typedef Elf64_Phdr ElfPhdr; -}; - -template <> -struct ElfTypes<32> { - typedef Elf32_Ehdr ElfEhdr; - typedef Elf32_Phdr ElfPhdr; -}; - -template <int Size> -struct ElfBinary { - using ElfEhdr = typename ElfTypes<Size>::ElfEhdr; - using ElfPhdr = typename ElfTypes<Size>::ElfPhdr; - - ElfEhdr header = {}; - std::vector<ElfPhdr> phdrs; - std::vector<char> data; - - // UpdateOffsets updates p_offset, p_vaddr in all phdrs to account for the - // space taken by the header and phdrs. - // - // It also updates header.e_phnum and adds the offset to header.e_entry to - // account for the headers residing in the first PT_LOAD segment. - // - // Before calling UpdateOffsets each of those fields should be the appropriate - // offset into data. - void UpdateOffsets() { - size_t offset = sizeof(header) + phdrs.size() * sizeof(ElfPhdr); - header.e_entry += offset; - header.e_phnum = phdrs.size(); - for (auto& p : phdrs) { - p.p_offset += offset; - p.p_vaddr += offset; - } - } - - // AddInterpreter adds a PT_INTERP segment with the passed contents. - // - // A later call to UpdateOffsets is required to make the new phdr valid. - void AddInterpreter(std::vector<char> contents) { - const int start = data.size(); - data.insert(data.end(), contents.begin(), contents.end()); - const int size = data.size() - start; - - ElfPhdr phdr = {}; - phdr.p_type = PT_INTERP; - phdr.p_offset = start; - phdr.p_filesz = size; - phdr.p_memsz = size; - // "If [PT_INTERP] is present, it must precede any loadable segment entry." - phdrs.insert(phdrs.begin(), phdr); - } - - // Writes the header, phdrs, and data to fd. - PosixError Write(int fd) const { - int ret = WriteFd(fd, &header, sizeof(header)); - if (ret < 0) { - return PosixError(errno, "failed to write header"); - } else if (ret != sizeof(header)) { - return PosixError(EIO, absl::StrCat("short write of header: ", ret)); - } - - for (auto const& p : phdrs) { - ret = WriteFd(fd, &p, sizeof(p)); - if (ret < 0) { - return PosixError(errno, "failed to write phdr"); - } else if (ret != sizeof(p)) { - return PosixError(EIO, absl::StrCat("short write of phdr: ", ret)); - } - } - - ret = WriteFd(fd, data.data(), data.size()); - if (ret < 0) { - return PosixError(errno, "failed to write data"); - } else if (ret != static_cast<int>(data.size())) { - return PosixError(EIO, absl::StrCat("short write of data: ", ret)); - } - - return NoError(); - } -}; - -// Creates a new temporary executable ELF file in parent with elf as the -// contents. -template <int Size> -PosixErrorOr<TempPath> CreateElfWith(absl::string_view parent, - ElfBinary<Size> const& elf) { - ASSIGN_OR_RETURN_ERRNO( - auto file, TempPath::CreateFileWith(parent, absl::string_view(), 0755)); - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(file.path(), O_RDWR)); - RETURN_IF_ERRNO(elf.Write(fd.get())); - return std::move(file); -} - -// Creates a new temporary executable ELF file with elf as the contents. -template <int Size> -PosixErrorOr<TempPath> CreateElfWith(ElfBinary<Size> const& elf) { - return CreateElfWith(GetAbsoluteTestTmpdir(), elf); -} - -// Wait for pid to stop, and assert that it stopped via SIGSTOP. -PosixError WaitStopped(pid_t pid) { - int status; - int ret = RetryEINTR(waitpid)(pid, &status, 0); - MaybeSave(); - if (ret < 0) { - return PosixError(errno, "wait failed"); - } else if (ret != pid) { - return PosixError(ESRCH, absl::StrCat("wait got ", ret, " want ", pid)); - } - - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { - return PosixError(EINVAL, - absl::StrCat("pid did not SIGSTOP; status = ", status)); - } - - return NoError(); -} - -// Returns a valid ELF that PTRACE_TRACEME and SIGSTOPs itself. -// -// UpdateOffsets must be called before writing this ELF. -ElfBinary<64> StandardElf() { - ElfBinary<64> elf; - elf.header.e_ident[EI_MAG0] = ELFMAG0; - elf.header.e_ident[EI_MAG1] = ELFMAG1; - elf.header.e_ident[EI_MAG2] = ELFMAG2; - elf.header.e_ident[EI_MAG3] = ELFMAG3; - elf.header.e_ident[EI_CLASS] = ELFCLASS64; - elf.header.e_ident[EI_DATA] = ELFDATA2LSB; - elf.header.e_ident[EI_VERSION] = EV_CURRENT; - elf.header.e_type = ET_EXEC; - elf.header.e_machine = EM_TYPE; - elf.header.e_version = EV_CURRENT; - elf.header.e_phoff = sizeof(elf.header); - elf.header.e_phentsize = sizeof(decltype(elf)::ElfPhdr); - - // TODO(gvisor.dev/issue/153): Always include a PT_GNU_STACK segment to - // disable executable stacks. With this omitted the stack (and all PROT_READ) - // mappings should be executable, but gVisor doesn't support that. - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_GNU_STACK; - phdr.p_flags = PF_R | PF_W; - elf.phdrs.push_back(phdr); - - phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x40000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.header.e_entry = phdr.p_vaddr; - - elf.data.assign(kPtraceCode, kPtraceCode + sizeof(kPtraceCode)); - - return elf; -} - -// Test that a trivial binary executes. -TEST(ElfTest, Execute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - // Ensure it made it to SIGSTOP. - ASSERT_NO_ERRNO(WaitStopped(child)); - - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - // RIP/PC is just beyond the final syscall instruction. - EXPECT_EQ(IP_REG(regs), elf.header.e_entry + sizeof(kPtraceCode)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// StandardElf without data completes execve, but faults once running. -TEST(ElfTest, MissingText) { - ElfBinary<64> elf = StandardElf(); - elf.data.clear(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - // It runs off the end of the zeroes filling the end of the page. -#if defined(__x86_64__) - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; -#elif defined(__aarch64__) - // 0 is an invalid instruction opcode on arm64. - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGILL) << status; -#endif -} - -// Typical ELF with a data + bss segment -TEST(ElfTest, DataSegment) { - ElfBinary<64> elf = StandardElf(); - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // data + bss page from file. - {0x41000, 0x42000, true, true, false, true, kPageSize, 0, 0, 0, - file.path().c_str()}, - // bss page from anon. - {0x42000, 0x43000, true, true, false, true, 0, 0, 0, 0, ""}, - }))); -} - -// Additonal pages beyond filesz honor (only) execute protections. -// -// N.B. Linux changed this in 4.11 (16e72e9b30986 "powerpc: do not make the -// entire heap executable"). Previously, extra pages were always RW. -TEST(ElfTest, ExtraMemPages) { - // gVisor has the newer behavior. - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major < 4 || (version.major == 4 && version.minor < 11)); - } - - ElfBinary<64> elf = StandardElf(); - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // RWX segment. The extra anon page will also be RWX. - // - // N.B. Linux uses clear_user to clear the end of the file-mapped page, which - // respects the mapping protections. Thus if we map this RO with memsz > - // (unaligned) filesz, then execve will fail with EFAULT. See padzero(elf_bss) - // in fs/binfmt_elf.c:load_elf_binary. - // - // N.N.B.B. The above only applies to the last segment. For earlier segments, - // the clear_user error is ignored. - phdr.p_flags = PF_R | PF_W | PF_X; - phdr.p_offset = kPageSize; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, - ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // data + bss page from file. - {0x41000, 0x42000, true, true, true, true, kPageSize, 0, 0, 0, - file.path().c_str()}, - // extra page from anon. - {0x42000, 0x43000, true, true, true, true, 0, 0, 0, 0, ""}, - }))); -} - -// An aligned segment with filesz == 0, memsz > 0 is anon-only. -TEST(ElfTest, AnonOnlySegment) { - ElfBinary<64> elf = StandardElf(); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // RO segment. The extra anon page will be RW anyways. - phdr.p_flags = PF_R; - phdr.p_offset = 0; - phdr.p_vaddr = 0x41000; - phdr.p_filesz = 0; - phdr.p_memsz = kPageSize; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - // UpdateOffsets adjusts p_vaddr and p_offset by the header size, but we need - // a page-aligned p_vaddr to get a truly anon-only page. - elf.phdrs[2].p_vaddr = 0x41000; - // N.B. p_offset is now unaligned, but Linux doesn't care since this is - // anon-only. - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, - ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - // anon page. - {0x41000, 0x42000, true, true, false, true, 0, 0, 0, 0, ""}, - }))); -} - -// p_offset must have the same alignment as p_vaddr. -TEST(ElfTest, UnalignedOffset) { - ElfBinary<64> elf = StandardElf(); - - // Unaligned offset. - elf.phdrs[1].p_offset += 1; - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - - // execve(2) return EINVAL, but behavior varies between Linux and gVisor. - // - // On Linux, the new mm is committed before attempting to map into it. By the - // time we hit EINVAL in the segment mmap, the old mm is gone. Linux returns - // to an empty mm, which immediately segfaults. - // - // OTOH, gVisor maps into the new mm before committing it. Thus when it hits - // failure, the caller is still intact to receive the error. - if (IsRunningOnGvisor()) { - ASSERT_EQ(execve_errno, EINVAL); - } else { - ASSERT_EQ(execve_errno, 0); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; - } -} - -// Linux will allow PT_LOAD segments to overlap. -TEST(ElfTest, DirectlyOverlappingSegments) { - // NOTE(b/37289926): see PIEOutOfOrderSegments. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - // Same as the StandardElf mapping. - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - // Add PF_W so we can differentiate this mapping from the first. - phdr.p_flags = PF_R | PF_W | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x40000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x40000, 0x41000, true, true, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// Linux allows out-of-order PT_LOAD segments. -TEST(ElfTest, OutOfOrderSegments) { - // NOTE(b/37289926): see PIEOutOfOrderSegments. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_X; - phdr.p_offset = 0; - phdr.p_vaddr = 0x20000; - phdr.p_filesz = sizeof(kPtraceCode); - phdr.p_memsz = phdr.p_filesz; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - {0x20000, 0x21000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - file.path().c_str()}, - }))); -} - -// header.e_phoff is bound the end of the file. -TEST(ElfTest, OutOfBoundsPhdrs) { - ElfBinary<64> elf = StandardElf(); - elf.header.e_phoff = 0x100000; - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC. - EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO))); -} - -// Claim there is a phdr beyond the end of the file, but don't include it. -TEST(ElfTest, MissingPhdr) { - ElfBinary<64> elf = StandardElf(); - - // Clear data so the file ends immediately after the phdrs. - // N.B. Per ElfTest.MissingData, StandardElf without data completes execve - // without error. - elf.data.clear(); - elf.UpdateOffsets(); - - // Claim that there is another phdr just beyond the end of the file. Of - // course, it isn't accessible. - elf.header.e_phnum++; - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - // On Linux 3.11, this caused EIO. On newer Linux, it causes ENOEXEC. - EXPECT_THAT(execve_errno, AnyOf(Eq(ENOEXEC), Eq(EIO))); -} - -// No headers at all, just the ELF magic. -TEST(ElfTest, MissingHeader) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0755)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - const char kElfMagic[] = {0x7f, 'E', 'L', 'F'}; - - ASSERT_THAT(WriteFd(fd.get(), &kElfMagic, sizeof(kElfMagic)), - SyscallSucceeds()); - fd.reset(); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOEXEC); -} - -// Load a PIE ELF with a data + bss segment. -TEST(ElfTest, PIE) { - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - elf.header.e_entry = 0x0; - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment at a bit of an offset. - phdr.p_vaddr = 0x20000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - const uint64_t offset = elf.phdrs[1].p_offset; - elf.phdrs[1].p_offset = 0x0; - elf.phdrs[1].p_vaddr = 0x0; - elf.phdrs[1].p_filesz += offset; - elf.phdrs[1].p_memsz += offset; - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment was loaded into. - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - const uint64_t load_addr = IP_REG(regs) & ~(kPageSize - 1); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {load_addr, load_addr + 0x1000, true, false, true, - true, 0, 0, 0, 0, file.path().c_str()}, - // data + bss page from file. - {load_addr + 0x20000, load_addr + 0x21000, true, true, - false, true, kPageSize, 0, 0, 0, file.path().c_str()}, - // bss page from anon. - {load_addr + 0x21000, load_addr + 0x22000, true, true, - false, true, 0, 0, 0, 0, ""}, - }))); -} - -// PIE binary with a non-zero start address. -// -// This is non-standard for a PIE binary, but valid. The binary is still loaded -// at an arbitrary address, not the first PT_LOAD vaddr. -// -// N.B. Linux changed this behavior in d1fd836dcf00d2028c700c7e44d2c23404062c90. -// Previously, with "randomization" enabled, PIE binaries with a non-zero start -// address would be be loaded at the address they specified because mmap was -// passed the load address, which wasn't 0 as expected. -// -// This change is present in kernel v4.1+. -TEST(ElfTest, PIENonZeroStart) { - // gVisor has the newer behavior. - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major < 4 || (version.major == 4 && version.minor < 1)); - } - - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment at a bit of an offset. - phdr.p_vaddr = 0x60000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment was loaded into. - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - const uint64_t load_addr = IP_REG(regs) & ~(kPageSize - 1); - - // The ELF is loaded at an arbitrary address, not the first PT_LOAD vaddr. - // - // N.B. this is technically flaky, but Linux is *extremely* unlikely to pick - // this as the start address, as it searches from the top down. - EXPECT_NE(load_addr, 0x40000); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // text page. - {load_addr, load_addr + 0x1000, true, false, true, - true, 0, 0, 0, 0, file.path().c_str()}, - // data + bss page from file. - {load_addr + 0x20000, load_addr + 0x21000, true, true, - false, true, kPageSize, 0, 0, 0, file.path().c_str()}, - // bss page from anon. - {load_addr + 0x21000, load_addr + 0x22000, true, true, - false, true, 0, 0, 0, 0, ""}, - }))); -} - -TEST(ElfTest, PIEOutOfOrderSegments) { - // TODO(b/37289926): This triggers a bug in Linux where it computes the size - // of the binary as 0x20000 - 0x40000 = 0xfffffffffffe0000, which obviously - // fails to map. - // - // We test gVisor's behavior (of rejecting the binary) because I assert that - // Linux is wrong and needs to be fixed. - SKIP_IF(!IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Create a standard ELF, but extend to 1.5 pages. The second page will be the - // beginning of a multi-page data + bss segment. - elf.data.resize(kPageSize + kPageSize / 2); - - decltype(elf)::ElfPhdr phdr = {}; - phdr.p_type = PT_LOAD; - phdr.p_flags = PF_R | PF_W; - phdr.p_offset = kPageSize; - // Put the data segment *before* the first segment. - phdr.p_vaddr = 0x20000; - phdr.p_filesz = kPageSize / 2; - // The header is going to push vaddr up by a few hundred bytes. Keep p_memsz a - // bit less than 2 pages so this mapping doesn't extend beyond 0x43000. - phdr.p_memsz = 2 * kPageSize - kPageSize / 2; - elf.phdrs.push_back(phdr); - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOEXEC); -} - -TEST(ElfTest, PIEOverflow) { - ElfBinary<64> elf = StandardElf(); - - elf.header.e_type = ET_DYN; - - // Choose vaddr of the first segment so that the end address overflows if the - // segment is mapped with a non-zero offset. - elf.phdrs[1].p_vaddr = 0xfffffffffffff000UL - elf.phdrs[1].p_memsz; - - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - if (IsRunningOnGvisor()) { - ASSERT_EQ(execve_errno, EINVAL); - } else { - ASSERT_EQ(execve_errno, 0); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; - } -} - -// Standard dynamically linked binary with an ELF interpreter. -TEST(ElfTest, ELFInterpreter) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // N.B. Since Linux 4.10 (0036d1f7eb95b "binfmt_elf: fix calculations for bss - // padding"), Linux unconditionally zeroes the remainder of the highest mapped - // page in an interpreter, failing if the protections don't allow write. Thus - // we must mark this writeable. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // Append the interpreter path. - int const interp_data_start = binary.data.size(); - for (char const c : interpreter_file.path()) { - binary.data.push_back(c); - } - // NUL-terminate. - binary.data.push_back(0); - int const interp_data_size = binary.data.size() - interp_data_start; - - decltype(binary)::ElfPhdr phdr = {}; - phdr.p_type = PT_INTERP; - phdr.p_offset = interp_data_start; - phdr.p_filesz = interp_data_size; - phdr.p_memsz = interp_data_size; - // "If [PT_INTERP] is present, it must precede any loadable segment entry." - // - // However, Linux allows it anywhere, so we just stick it at the end to make - // sure out-of-order PT_INTERP is OK. - binary.phdrs.push_back(phdr); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment of the interpreter was loaded - // into. - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - const uint64_t interp_load_addr = IP_REG(regs) & ~(kPageSize - 1); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Main binary - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - binary_file.path().c_str()}, - // Interpreter - {interp_load_addr, interp_load_addr + 0x1000, true, true, true, - true, 0, 0, 0, 0, interpreter_file.path().c_str()}, - }))); -} - -// Test parameter to ElfInterpterStaticTest cases. The first item is a suffix to -// add to the end of the interpreter path in the PT_INTERP segment and the -// second is the expected execve(2) errno. -using ElfInterpreterStaticParam = std::tuple<std::vector<char>, int>; - -class ElfInterpreterStaticTest - : public ::testing::TestWithParam<ElfInterpreterStaticParam> {}; - -// Statically linked ELF with a statically linked ELF interpreter. -TEST_P(ElfInterpreterStaticTest, Test) { - // TODO(gvisor.dev/issue/3721): Test has been observed to segfault on 5.X - // kernels. - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major > 4); - } - - const std::vector<char> segment_suffix = std::get<0>(GetParam()); - const int expected_errno = std::get<1>(GetParam()); - - ElfBinary<64> interpreter = StandardElf(); - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.UpdateOffsets(); - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - // The PT_LOAD segment conflicts with the interpreter's PT_LOAD segment. The - // interpreter's will be mapped directly over the binary's. - - // Interpreter path plus the parameterized suffix in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.insert(segment.end(), segment_suffix.begin(), segment_suffix.end()); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, expected_errno); - - if (expected_errno == 0) { - ASSERT_NO_ERRNO(WaitStopped(child)); - - EXPECT_THAT(child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Interpreter. - {0x40000, 0x41000, true, true, true, true, 0, 0, 0, - 0, interpreter_file.path().c_str()}, - }))); - } -} - -INSTANTIATE_TEST_SUITE_P( - Cases, ElfInterpreterStaticTest, - ::testing::ValuesIn({ - // Simple NUL-terminator to run the interpreter as normal. - std::make_tuple(std::vector<char>({'\0'}), 0), - // Add some garbage to the segment followed by a NUL-terminator. This is - // ignored. - std::make_tuple(std::vector<char>({'\0', 'b', '\0'}), 0), - // Add some garbage to the segment without a NUL-terminator. Linux will - // reject - // this. - std::make_tuple(std::vector<char>({'\0', 'b'}), ENOEXEC), - })); - -// Test parameter to ElfInterpterBadPathTest cases. The first item is the -// contents of the PT_INTERP segment and the second is the expected execve(2) -// errno. -using ElfInterpreterBadPathParam = std::tuple<std::vector<char>, int>; - -class ElfInterpreterBadPathTest - : public ::testing::TestWithParam<ElfInterpreterBadPathParam> {}; - -TEST_P(ElfInterpreterBadPathTest, Test) { - const std::vector<char> segment = std::get<0>(GetParam()); - const int expected_errno = std::get<1>(GetParam()); - - ElfBinary<64> binary = StandardElf(); - binary.AddInterpreter(segment); - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, expected_errno); -} - -INSTANTIATE_TEST_SUITE_P( - Cases, ElfInterpreterBadPathTest, - ::testing::ValuesIn({ - // NUL-terminated fake path in the PT_INTERP segment. - std::make_tuple(std::vector<char>({'/', 'f', '/', 'b', '\0'}), ENOENT), - // ELF interpreter not NUL-terminated. - std::make_tuple(std::vector<char>({'/', 'f', '/', 'b'}), ENOEXEC), - // ELF interpreter path omitted entirely. - // - // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2 - // bytes. - std::make_tuple(std::vector<char>({'\0'}), ENOEXEC), - // ELF interpreter path = "\0". - // - // fs/binfmt_elf.c:load_elf_binary returns ENOEXEC if p_filesz is < 2 - // bytes, so add an extra byte to pass that check. - // - // load_elf_binary -> open_exec -> do_open_execat fails to check that - // name != '\0' before calling do_filp_open, which thus opens the - // working directory. do_open_execat returns EACCES because the - // directory is not a regular file. - std::make_tuple(std::vector<char>({'\0', '\0'}), EACCES), - })); - -// Relative path to ELF interpreter. -TEST(ElfTest, ELFInterpreterRelative) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto interpreter_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, interpreter_file.path())); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - std::vector<char> segment(interpreter_relative.begin(), - interpreter_relative.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // RIP tells us which page the first segment of the interpreter was loaded - // into. - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - const uint64_t interp_load_addr = IP_REG(regs) & ~(kPageSize - 1); - - EXPECT_THAT( - child, ContainsMappings(std::vector<ProcMapsEntry>({ - // Main binary - {0x40000, 0x41000, true, false, true, true, 0, 0, 0, 0, - binary_file.path().c_str()}, - // Interpreter - {interp_load_addr, interp_load_addr + 0x1000, true, true, true, - true, 0, 0, 0, 0, interpreter_file.path().c_str()}, - }))); -} - -// ELF interpreter architecture doesn't match the binary. -TEST(ElfTest, ELFInterpreterWrongArch) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_machine = EM_PPC64; - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - binary_file.path(), {binary_file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, ELIBBAD); -} - -// No execute permissions on the binary. -TEST(ElfTest, NoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - ASSERT_THAT(chmod(file.path().c_str(), 0644), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Execute, but no read permissions on the binary works just fine. -TEST(ElfTest, NoRead) { - // TODO(gvisor.dev/issue/160): gVisor's backing filesystem may prevent the - // sentry from reading the executable. - SKIP_IF(IsRunningOnGvisor()); - - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - ASSERT_THAT(chmod(file.path().c_str(), 0111), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - ASSERT_NO_ERRNO(WaitStopped(child)); - - // TODO(gvisor.dev/issue/160): A task with a non-readable executable is marked - // non-dumpable, preventing access to proc files. gVisor does not implement - // this behavior. -} - -// No execute permissions on the ELF interpreter. -TEST(ElfTest, ElfInterpreterNoExecute) { - ElfBinary<64> interpreter = StandardElf(); - interpreter.header.e_type = ET_DYN; - interpreter.header.e_entry = 0x0; - interpreter.UpdateOffsets(); - - // The first segment really needs to start at 0 for a normal PIE binary, and - // thus includes the headers. - uint64_t const offset = interpreter.phdrs[1].p_offset; - // See comment in ElfTest.ELFInterpreter. - interpreter.phdrs[1].p_flags = PF_R | PF_W | PF_X; - interpreter.phdrs[1].p_offset = 0x0; - interpreter.phdrs[1].p_vaddr = 0x0; - interpreter.phdrs[1].p_filesz += offset; - interpreter.phdrs[1].p_memsz += offset; - - TempPath interpreter_file = - ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(interpreter)); - - ElfBinary<64> binary = StandardElf(); - - // NUL-terminated path in the PT_INTERP segment. - const std::string path = interpreter_file.path(); - std::vector<char> segment(path.begin(), path.end()); - segment.push_back(0); - binary.AddInterpreter(segment); - - binary.UpdateOffsets(); - - TempPath binary_file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(binary)); - - ASSERT_THAT(chmod(interpreter_file.path().c_str(), 0644), SyscallSucceeds()); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(interpreter_file.path(), {interpreter_file.path()}, {}, - &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Execute a basic interpreter script. -TEST(InterpreterScriptTest, Execute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Whitespace after #!. -TEST(InterpreterScriptTest, Whitespace) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#! \t \t", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script is missing execute permission. -TEST(InterpreterScriptTest, InterpreterScriptNoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0644)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, EACCES); -} - -// Binary interpreter script refers to is missing execute permission. -TEST(InterpreterScriptTest, BinaryNoExecute) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - ASSERT_THAT(chmod(binary.path().c_str(), 0644), SyscallSucceeds()); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, EACCES); -} - -// Linux will load interpreter scripts five levels deep, but no more. -TEST(InterpreterScriptTest, MaxRecursion) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", binary.path()), 0755)); - TempPath script2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script1.path()), 0755)); - TempPath script3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script2.path()), 0755)); - TempPath script4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script3.path()), 0755)); - TempPath script5 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script4.path()), 0755)); - TempPath script6 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - "/tmp", absl::StrCat("#!", script5.path()), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script6.path(), {script6.path()}, {}, &child, &execve_errno)); - // Too many levels of recursion. - EXPECT_EQ(execve_errno, ELOOP); - - // The next level up is OK. - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script5.path(), {script5.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script with a relative path. -TEST(InterpreterScriptTest, RelativePath) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - auto cwd = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - auto binary_relative = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, binary.path())); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary_relative), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script with .. in a path component. -TEST(InterpreterScriptTest, UncleanPath) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!/tmp/../", binary.path()), - 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Passed interpreter script is a symlink. -TEST(InterpreterScriptTest, Symlink) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - // Use /tmp explicitly to ensure the path is short enough. - TempPath binary = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith("/tmp", elf)); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", binary.path()), 0755)); - - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), script.path())); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(link.path(), {link.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - EXPECT_NO_ERRNO(WaitStopped(child)); -} - -// Interpreter script points to a symlink loop. -TEST(InterpreterScriptTest, SymlinkLoop) { - std::string const link1 = NewTempAbsPathInDir("/tmp"); - std::string const link2 = NewTempAbsPathInDir("/tmp"); - - ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds()); - auto remove_link1 = Cleanup( - [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); }); - - ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds()); - auto remove_link2 = Cleanup( - [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); }); - - TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::StrCat("#!", link1), 0755)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(script.path(), {script.path()}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -// Binary is a symlink loop. -TEST(ExecveTest, SymlinkLoop) { - std::string const link1 = NewTempAbsPathInDir("/tmp"); - std::string const link2 = NewTempAbsPathInDir("/tmp"); - - ASSERT_THAT(symlink(link2.c_str(), link1.c_str()), SyscallSucceeds()); - auto remove_link = Cleanup( - [&link1] { EXPECT_THAT(unlink(link1.c_str()), SyscallSucceeds()); }); - - ASSERT_THAT(symlink(link1.c_str(), link2.c_str()), SyscallSucceeds()); - auto remove_link2 = Cleanup( - [&link2] { EXPECT_THAT(unlink(link2.c_str()), SyscallSucceeds()); }); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(link1, {link1}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ELOOP); -} - -// Binary is a directory. -TEST(ExecveTest, Directory) { - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/tmp", {"/tmp"}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -// Pass a valid binary as a directory (extra / on the end). -TEST(ExecveTest, BinaryAsDirectory) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - std::string const path = absl::StrCat(file.path(), "/"); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(path, {path}, {}, &child, &execve_errno)); - EXPECT_EQ(execve_errno, ENOTDIR); -} - -// The initial brk value is after the page at the end of the binary. -TEST(ExecveTest, BrkAfterBinary) { - ElfBinary<64> elf = StandardElf(); - elf.UpdateOffsets(); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); - - pid_t child; - int execve_errno; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); - ASSERT_EQ(execve_errno, 0); - - // Ensure it made it to SIGSTOP. - ASSERT_NO_ERRNO(WaitStopped(child)); - - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - // RIP is just beyond the final syscall instruction. Rewind to execute a brk - // syscall. - IP_REG(regs) -= kSyscallSize; - RAX_REG(regs) = __NR_brk; - RDI_REG(regs) = 0; - ASSERT_THAT(ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - - // Resume the child, waiting for syscall entry. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << "status = " << status; - - // Execute the syscall. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child, 0, 0), SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << "status = " << status; - - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov), - SyscallSucceeds()); - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - - // brk is after the text page. - // - // The kernel does brk randomization, so we can't be sure what the exact - // address will be, but it is always beyond the final page in the binary. - // i.e., it does not start immediately after memsz in the middle of a page. - // Userspace may expect to use that space. - EXPECT_GE(RETURN_REG(regs), 0x41000); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/exec_proc_exe_workload.cc b/test/syscalls/linux/exec_proc_exe_workload.cc deleted file mode 100644 index 2989379b7..000000000 --- a/test/syscalls/linux/exec_proc_exe_workload.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdlib.h> -#include <unistd.h> - -#include <iostream> - -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" - -int main(int argc, char** argv, char** envp) { - // This is annoying. Because remote build systems may put these binaries - // in a content-addressable-store, you may wind up with /proc/self/exe - // pointing to some random path (but with a sensible argv[0]). - // - // Therefore, this test simply checks that the /proc/self/exe - // is absolute and *doesn't* match argv[1]. - std::string exe = - gvisor::testing::ProcessExePath(getpid()).ValueOrDie(); - if (exe[0] != '/') { - std::cerr << "relative path: " << exe << std::endl; - exit(1); - } - if (exe.find(argv[1]) != std::string::npos) { - std::cerr << "matching path: " << exe << std::endl; - exit(1); - } - - return 0; -} diff --git a/test/syscalls/linux/exec_state_workload.cc b/test/syscalls/linux/exec_state_workload.cc deleted file mode 100644 index 028902b14..000000000 --- a/test/syscalls/linux/exec_state_workload.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/auxv.h> -#include <sys/prctl.h> -#include <sys/time.h> - -#include <iostream> -#include <ostream> -#include <string> - -#include "absl/strings/numbers.h" - -// Pretty-print a sigset_t. -std::ostream& operator<<(std::ostream& out, const sigset_t& s) { - out << "{ "; - - for (int i = 0; i < NSIG; i++) { - if (sigismember(&s, i)) { - out << i << " "; - } - } - - out << "}"; - return out; -} - -// Verify that the signo handler is handler. -int CheckSigHandler(uint32_t signo, uintptr_t handler) { - struct sigaction sa; - int ret = sigaction(signo, nullptr, &sa); - if (ret < 0) { - perror("sigaction"); - return 1; - } - - if (reinterpret_cast<void (*)(int)>(handler) != sa.sa_handler) { - std::cerr << "signo " << signo << " handler got: " << sa.sa_handler - << " expected: " << std::hex << handler; - return 1; - } - return 0; -} - -// Verify that the signo is blocked. -int CheckSigBlocked(uint32_t signo) { - sigset_t s; - int ret = sigprocmask(SIG_SETMASK, nullptr, &s); - if (ret < 0) { - perror("sigprocmask"); - return 1; - } - - if (!sigismember(&s, signo)) { - std::cerr << "signal " << signo << " not blocked in signal mask: " << s - << std::endl; - return 1; - } - return 0; -} - -// Verify that the itimer is enabled. -int CheckItimerEnabled(uint32_t timer) { - struct itimerval itv; - int ret = getitimer(timer, &itv); - if (ret < 0) { - perror("getitimer"); - return 1; - } - - if (!itv.it_value.tv_sec && !itv.it_value.tv_usec && - !itv.it_interval.tv_sec && !itv.it_interval.tv_usec) { - std::cerr << "timer " << timer - << " not enabled. value sec: " << itv.it_value.tv_sec - << " usec: " << itv.it_value.tv_usec - << " interval sec: " << itv.it_interval.tv_sec - << " usec: " << itv.it_interval.tv_usec << std::endl; - return 1; - } - return 0; -} - -int PrintExecFn() { - unsigned long execfn = getauxval(AT_EXECFN); - if (!execfn) { - std::cerr << "AT_EXECFN missing" << std::endl; - return 1; - } - - std::cerr << reinterpret_cast<const char*>(execfn) << std::endl; - return 0; -} - -int PrintExecName() { - const size_t name_length = 20; - char name[name_length] = {0}; - if (prctl(PR_GET_NAME, name) < 0) { - std::cerr << "prctl(PR_GET_NAME) failed" << std::endl; - return 1; - } - - std::cerr << name << std::endl; - return 0; -} - -void usage(const std::string& prog) { - std::cerr << "usage:\n" - << "\t" << prog << " CheckSigHandler <signo> <handler addr (hex)>\n" - << "\t" << prog << " CheckSigBlocked <signo>\n" - << "\t" << prog << " CheckTimerDisabled <timer>\n" - << "\t" << prog << " PrintExecFn\n" - << "\t" << prog << " PrintExecName" << std::endl; -} - -int main(int argc, char** argv) { - if (argc < 2) { - usage(argv[0]); - return 1; - } - - std::string func(argv[1]); - - if (func == "CheckSigHandler") { - if (argc != 4) { - usage(argv[0]); - return 1; - } - - uint32_t signo; - if (!absl::SimpleAtoi(argv[2], &signo)) { - std::cerr << "invalid signo: " << argv[2] << std::endl; - return 1; - } - - uintptr_t handler; - if (!absl::numbers_internal::safe_strtoi_base(argv[3], &handler, 16)) { - std::cerr << "invalid handler: " << std::hex << argv[3] << std::endl; - return 1; - } - - return CheckSigHandler(signo, handler); - } - - if (func == "CheckSigBlocked") { - if (argc != 3) { - usage(argv[0]); - return 1; - } - - uint32_t signo; - if (!absl::SimpleAtoi(argv[2], &signo)) { - std::cerr << "invalid signo: " << argv[2] << std::endl; - return 1; - } - - return CheckSigBlocked(signo); - } - - if (func == "CheckItimerEnabled") { - if (argc != 3) { - usage(argv[0]); - return 1; - } - - uint32_t timer; - if (!absl::SimpleAtoi(argv[2], &timer)) { - std::cerr << "invalid signo: " << argv[2] << std::endl; - return 1; - } - - return CheckItimerEnabled(timer); - } - - if (func == "PrintExecFn") { - // N.B. This will be called as an interpreter script, with the script passed - // as the third argument. We don't care about that script. - return PrintExecFn(); - } - - if (func == "PrintExecName") { - // N.B. This may be called as an interpreter script like PrintExecFn. - return PrintExecName(); - } - - std::cerr << "Invalid function: " << func << std::endl; - return 1; -} diff --git a/test/syscalls/linux/exit.cc b/test/syscalls/linux/exit.cc deleted file mode 100644 index d52ea786b..000000000 --- a/test/syscalls/linux/exit.cc +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/wait.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" -#include "test/util/time_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void TestExit(int code) { - pid_t pid = fork(); - if (pid == 0) { - _exit(code); - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == code) << status; -} - -TEST(ExitTest, Success) { TestExit(0); } - -TEST(ExitTest, Failure) { TestExit(1); } - -// This test ensures that a process's file descriptors are closed when it calls -// exit(). In order to test this, the parent tries to read from a pipe whose -// write end is held by the child. While the read is blocking, the child exits, -// which should cause the parent to read 0 bytes due to EOF. -TEST(ExitTest, CloseFds) { - int pipe_fds[2]; - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - FileDescriptor read_fd(pipe_fds[0]); - FileDescriptor write_fd(pipe_fds[1]); - - pid_t pid = fork(); - if (pid == 0) { - read_fd.reset(); - - SleepSafe(absl::Seconds(10)); - - _exit(0); - } - - EXPECT_THAT(pid, SyscallSucceeds()); - - write_fd.reset(); - - char buf[10]; - EXPECT_THAT(ReadFd(read_fd.get(), buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/exit_script.sh b/test/syscalls/linux/exit_script.sh deleted file mode 100755 index 527518e06..000000000 --- a/test/syscalls/linux/exit_script.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -if [ $# -ne 1 ]; then - echo "Usage: $0 exit_code" - exit 255 -fi - -exit $1 diff --git a/test/syscalls/linux/fadvise64.cc b/test/syscalls/linux/fadvise64.cc deleted file mode 100644 index ac24c4066..000000000 --- a/test/syscalls/linux/fadvise64.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <syscall.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -TEST(FAdvise64Test, Basic) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // fadvise64 is noop in gVisor, so just test that it succeeds. - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), - SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_RANDOM), - SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_SEQUENTIAL), - SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_WILLNEED), - SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_DONTNEED), - SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NOREUSE), - SyscallSucceeds()); -} - -TEST(FAdvise64Test, FAdvise64WithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), - SyscallFailsWithErrno(EBADF)); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), - SyscallFailsWithErrno(EBADF)); -} - -TEST(FAdvise64Test, InvalidArgs) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // Note: offset is allowed to be negative. - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, static_cast<off_t>(-1), - POSIX_FADV_NORMAL), - SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, 12345), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(FAdvise64Test, NoPipes) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor read(fds[0]); - const FileDescriptor write(fds[1]); - - ASSERT_THAT(syscall(__NR_fadvise64, read.get(), 0, 10, POSIX_FADV_NORMAL), - SyscallFailsWithErrno(ESPIPE)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc deleted file mode 100644 index 5c839447e..000000000 --- a/test/syscalls/linux/fallocate.cc +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <signal.h> -#include <sys/eventfd.h> -#include <sys/resource.h> -#include <sys/signalfd.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/timerfd.h> -#include <syscall.h> -#include <time.h> -#include <unistd.h> - -#include <ctime> - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/file_base.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/cleanup.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -int fallocate(int fd, int mode, off_t offset, off_t len) { - return RetryEINTR(syscall)(__NR_fallocate, fd, mode, offset, len); -} - -class AllocateTest : public FileTest { - void SetUp() override { FileTest::SetUp(); } -}; - -TEST_F(AllocateTest, Fallocate) { - // Check that it starts at size zero. - struct stat buf; - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); - - // Grow to ten bytes. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 0, 10), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 10); - - // Allocate to a smaller size should be noop. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 0, 5), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 10); - - // Grow again. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 0, 20), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 20); - - // Grow with offset. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 10, 20), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 30); - - // Grow with offset beyond EOF. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 39, 1), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 40); - - // Given length 0 should fail with EINVAL. - ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 50, 0), - SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 40); -} - -TEST_F(AllocateTest, FallocateInvalid) { - // Invalid FD - EXPECT_THAT(fallocate(-1, 0, 0, 10), SyscallFailsWithErrno(EBADF)); - - // Negative offset and size. - EXPECT_THAT(fallocate(test_file_fd_.get(), 0, -1, 10), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, -1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(fallocate(test_file_fd_.get(), 0, -1, -1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(AllocateTest, FallocateReadonly) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(AllocateTest, FallocateWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(AllocateTest, FallocatePipe) { - int pipes[2]; - EXPECT_THAT(pipe(pipes), SyscallSucceeds()); - auto cleanup = Cleanup([&pipes] { - EXPECT_THAT(close(pipes[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipes[1]), SyscallSucceeds()); - }); - - EXPECT_THAT(fallocate(pipes[1], 0, 0, 10), SyscallFailsWithErrno(ESPIPE)); -} - -TEST_F(AllocateTest, FallocateChar) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_RDWR)); - EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); -} - -TEST_F(AllocateTest, FallocateRlimit) { - // Get the current rlimit and restore after test run. - struct rlimit initial_lim; - ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - auto cleanup = Cleanup([&initial_lim] { - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - }); - - // Try growing past the file size limit. - sigset_t new_mask; - sigemptyset(&new_mask); - sigaddset(&new_mask, SIGXFSZ); - sigprocmask(SIG_BLOCK, &new_mask, nullptr); - - struct rlimit setlim = {}; - setlim.rlim_cur = 1024; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds()); - - EXPECT_THAT(fallocate(test_file_fd_.get(), 0, 0, 1025), - SyscallFailsWithErrno(EFBIG)); - - struct timespec timelimit = {}; - timelimit.tv_sec = 10; - EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ); - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds()); -} - -TEST_F(AllocateTest, FallocateOtherFDs) { - int fd; - ASSERT_THAT(fd = timerfd_create(CLOCK_MONOTONIC, 0), SyscallSucceeds()); - auto timer_fd = FileDescriptor(fd); - EXPECT_THAT(fallocate(timer_fd.get(), 0, 0, 10), - SyscallFailsWithErrno(ENODEV)); - - sigset_t mask; - sigemptyset(&mask); - ASSERT_THAT(fd = signalfd(-1, &mask, 0), SyscallSucceeds()); - auto sfd = FileDescriptor(fd); - EXPECT_THAT(fallocate(sfd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); - - auto efd = - ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE)); - EXPECT_THAT(fallocate(efd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); - - auto sockfd = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - EXPECT_THAT(fallocate(sockfd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); - - int socks[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, socks), - SyscallSucceeds()); - auto sock0 = FileDescriptor(socks[0]); - auto sock1 = FileDescriptor(socks[1]); - EXPECT_THAT(fallocate(sock0.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV)); - - int pipefds[2]; - ASSERT_THAT(pipe(pipefds), SyscallSucceeds()); - EXPECT_THAT(fallocate(pipefds[1], 0, 0, 10), SyscallFailsWithErrno(ESPIPE)); - close(pipefds[0]); - close(pipefds[1]); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fault.cc b/test/syscalls/linux/fault.cc deleted file mode 100644 index bd87d5e60..000000000 --- a/test/syscalls/linux/fault.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#define _GNU_SOURCE 1 -#include <signal.h> -#include <ucontext.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -__attribute__((noinline)) void Fault(void) { - volatile int* foo = nullptr; - *foo = 0; -} - -int GetPcFromUcontext(ucontext_t* uc, uintptr_t* pc) { -#if defined(__x86_64__) - *pc = uc->uc_mcontext.gregs[REG_RIP]; - return 1; -#elif defined(__i386__) - *pc = uc->uc_mcontext.gregs[REG_EIP]; - return 1; -#elif defined(__aarch64__) - *pc = uc->uc_mcontext.pc; - return 1; -#else - return 0; -#endif -} - -void sigact_handler(int sig, siginfo_t* siginfo, void* context) { - uintptr_t pc; - if (GetPcFromUcontext(reinterpret_cast<ucontext_t*>(context), &pc)) { - /* Expect Fault() to be at most 64 bytes in size. */ - uintptr_t fault_addr = reinterpret_cast<uintptr_t>(&Fault); - EXPECT_GE(pc, fault_addr); - EXPECT_LT(pc, fault_addr + 64); - - // The following file is used to detect tests that exit prematurely. Since - // we need to call exit() here, delete the file by hand. - const char* exit_file = getenv("TEST_PREMATURE_EXIT_FILE"); - if (exit_file != nullptr) { - ASSERT_THAT(unlink(exit_file), SyscallSucceeds()); - } - exit(0); - } -} - -TEST(FaultTest, InRange) { - // Reset the signal handler to do nothing so that it doesn't freak out - // the test runner when we fire an alarm. - struct sigaction sa = {}; - sa.sa_sigaction = sigact_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - ASSERT_THAT(sigaction(SIGSEGV, &sa, nullptr), SyscallSucceeds()); - - Fault(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fchdir.cc b/test/syscalls/linux/fchdir.cc deleted file mode 100644 index c6675802d..000000000 --- a/test/syscalls/linux/fchdir.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(FchdirTest, Success) { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - int fd; - ASSERT_THAT(fd = open(temp_dir.path().c_str(), O_DIRECTORY | O_RDONLY), - SyscallSucceeds()); - - EXPECT_THAT(fchdir(fd), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - // Change CWD to a permanent location as temp dirs will be cleaned up. - EXPECT_THAT(chdir("/"), SyscallSucceeds()); -} - -TEST(FchdirTest, InvalidFD) { - EXPECT_THAT(fchdir(-1), SyscallFailsWithErrno(EBADF)); -} - -TEST(FchdirTest, PermissionDenied) { - // Drop capabilities that allow us to override directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */)); - - int fd; - ASSERT_THAT(fd = open(temp_dir.path().c_str(), O_DIRECTORY | O_RDONLY), - SyscallSucceeds()); - - EXPECT_THAT(fchdir(fd), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(FchdirTest, NotDir) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - int fd; - ASSERT_THAT(fd = open(temp_file.path().c_str(), O_CREAT | O_RDONLY, 0777), - SyscallSucceeds()); - - EXPECT_THAT(fchdir(fd), SyscallFailsWithErrno(ENOTDIR)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(FchdirTest, FchdirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_dir.path(), O_PATH)); - ASSERT_THAT(open(temp_dir.path().c_str(), O_DIRECTORY | O_PATH), - SyscallSucceeds()); - - EXPECT_THAT(fchdir(fd.get()), SyscallSucceeds()); - // Change CWD to a permanent location as temp dirs will be cleaned up. - EXPECT_THAT(chdir("/"), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc deleted file mode 100644 index 4fa6751ff..000000000 --- a/test/syscalls/linux/fcntl.cc +++ /dev/null @@ -1,1999 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <signal.h> -#include <sys/epoll.h> -#include <sys/mman.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include <atomic> -#include <deque> -#include <iostream> -#include <list> -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "absl/base/port.h" -#include "absl/flags/flag.h" -#include "absl/memory/memory.h" -#include "absl/strings/str_cat.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/signal_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -ABSL_FLAG(std::string, child_set_lock_on, "", - "Contains the path to try to set a file lock on."); -ABSL_FLAG(bool, child_set_lock_write, false, - "Whether to set a writable lock (otherwise readable)"); -ABSL_FLAG(bool, blocking, false, - "Whether to set a blocking lock (otherwise non-blocking)."); -ABSL_FLAG(bool, retry_eintr, false, - "Whether to retry in the subprocess on EINTR."); -ABSL_FLAG(uint64_t, child_set_lock_start, 0, "The value of struct flock start"); -ABSL_FLAG(uint64_t, child_set_lock_len, 0, "The value of struct flock len"); -ABSL_FLAG(int32_t, socket_fd, -1, - "A socket to use for communicating more state back " - "to the parent."); - -namespace gvisor { -namespace testing { - -std::function<void(int, siginfo_t*, void*)> setsig_signal_handle; -void setsig_signal_handler(int signum, siginfo_t* siginfo, void* ucontext) { - setsig_signal_handle(signum, siginfo, ucontext); -} - -class FcntlLockTest : public ::testing::Test { - public: - void SetUp() override { - // Let's make a socket pair. - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, fds_), SyscallSucceeds()); - } - - void TearDown() override { - EXPECT_THAT(close(fds_[0]), SyscallSucceeds()); - EXPECT_THAT(close(fds_[1]), SyscallSucceeds()); - } - - int64_t GetSubprocessFcntlTimeInUsec() { - int64_t ret = 0; - EXPECT_THAT(ReadFd(fds_[0], reinterpret_cast<void*>(&ret), sizeof(ret)), - SyscallSucceedsWithValue(sizeof(ret))); - return ret; - } - - // The first fd will remain with the process creating the subprocess - // and the second will go to the subprocess. - int fds_[2] = {}; -}; - -struct SignalDelivery { - int num; - siginfo_t info; -}; - -class FcntlSignalTest : public ::testing::Test { - public: - void SetUp() override { - int pipe_fds[2]; - ASSERT_THAT(pipe2(pipe_fds, O_NONBLOCK), SyscallSucceeds()); - pipe_read_fd_ = pipe_fds[0]; - pipe_write_fd_ = pipe_fds[1]; - } - - PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { - struct sigaction handler; - handler.sa_sigaction = setsig_signal_handler; - setsig_signal_handle = [&](int signum, siginfo_t* siginfo, - void* unused_ucontext) { - SignalDelivery sig; - sig.num = signum; - sig.info = *siginfo; - signals_received_.push_back(sig); - num_signals_received_++; - }; - sigemptyset(&handler.sa_mask); - handler.sa_flags = SA_SIGINFO; - return ScopedSigaction(signum, handler); - } - - void FlushAndCloseFD(int fd) { - char buf; - int read_bytes; - do { - read_bytes = read(fd, &buf, 1); - } while (read_bytes > 0); - // read() can also fail with EWOULDBLOCK since the pipe is open in - // non-blocking mode. This is not an error. - EXPECT_TRUE(read_bytes == 0 || (read_bytes == -1 && errno == EWOULDBLOCK)); - EXPECT_THAT(close(fd), SyscallSucceeds()); - } - - void DupReadFD() { - ASSERT_THAT(pipe_read_fd_dup_ = dup(pipe_read_fd_), SyscallSucceeds()); - max_expected_signals++; - } - - void RegisterFD(int fd, int signum) { - ASSERT_THAT(fcntl(fd, F_SETOWN, getpid()), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd, F_SETSIG, signum), SyscallSucceeds()); - int old_flags; - ASSERT_THAT(old_flags = fcntl(fd, F_GETFL), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd, F_SETFL, old_flags | O_ASYNC), SyscallSucceeds()); - } - - void GenerateIOEvent() { - ASSERT_THAT(write(pipe_write_fd_, "test", 4), SyscallSucceedsWithValue(4)); - } - - void WaitForSignalDelivery(absl::Duration timeout) { - absl::Time wait_start = absl::Now(); - while (num_signals_received_ < max_expected_signals && - absl::Now() - wait_start < timeout) { - absl::SleepFor(absl::Milliseconds(10)); - } - } - - int pipe_read_fd_ = -1; - int pipe_read_fd_dup_ = -1; - int pipe_write_fd_ = -1; - int max_expected_signals = 1; - std::deque<SignalDelivery> signals_received_; - std::atomic<int> num_signals_received_ = 0; -}; - -namespace { - -PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write, - bool blocking, bool retry_eintr, int fd, - off_t start, off_t length, pid_t* child) { - std::vector<std::string> args = { - "/proc/self/exe", "--child_set_lock_on", path, - "--child_set_lock_start", absl::StrCat(start), "--child_set_lock_len", - absl::StrCat(length), "--socket_fd", absl::StrCat(fd)}; - - if (for_write) { - args.push_back("--child_set_lock_write"); - } - - if (blocking) { - args.push_back("--blocking"); - } - - if (retry_eintr) { - args.push_back("--retry_eintr"); - } - - int execve_errno = 0; - ASSIGN_OR_RETURN_ERRNO( - auto cleanup, - ForkAndExec("/proc/self/exe", ExecveArray(args.begin(), args.end()), {}, - nullptr, child, &execve_errno)); - - if (execve_errno != 0) { - return PosixError(execve_errno, "execve"); - } - - return std::move(cleanup); -} - -TEST(FcntlTest, FcntlDupWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); - - int new_fd; - // Dup the descriptor and make sure it's the same file. - EXPECT_THAT(new_fd = fcntl(fd.get(), F_DUPFD, 0), SyscallSucceeds()); - - FileDescriptor nfd = FileDescriptor(new_fd); - ASSERT_NE(fd.get(), nfd.get()); - ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); - EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(O_PATH)); -} - -TEST(FcntlTest, SetFileStatusFlagWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - - EXPECT_THAT(fcntl(fd.get(), F_SETFL, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(FcntlTest, BadFcntlsWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - - EXPECT_THAT(fcntl(fd.get(), F_SETOWN, 0), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(fcntl(fd.get(), F_GETOWN, 0), SyscallFailsWithErrno(EBADF)); - - EXPECT_THAT(fcntl(fd.get(), F_SETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(fcntl(fd.get(), F_GETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(FcntlTest, SetCloExecBadFD) { - // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set. - FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - auto fd = f.get(); - f.reset(); - ASSERT_THAT(fcntl(fd, F_GETFD), SyscallFailsWithErrno(EBADF)); - ASSERT_THAT(fcntl(fd, F_SETFD, FD_CLOEXEC), SyscallFailsWithErrno(EBADF)); -} - -TEST(FcntlTest, SetCloExec) { - // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set. - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - - // Set the FD_CLOEXEC flag. - ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST(FcntlTest, SetCloExecWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - // Open a file descriptor with FD_CLOEXEC descriptor flag not set. - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - - // Set the FD_CLOEXEC flag. - ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST(FcntlTest, DupFDCloExecWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - // Open a file descriptor with FD_CLOEXEC descriptor flag not set. - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - int nfd; - ASSERT_THAT(nfd = fcntl(fd.get(), F_DUPFD_CLOEXEC, 0), SyscallSucceeds()); - FileDescriptor dup_fd(nfd); - - // Check for the FD_CLOEXEC flag. - ASSERT_THAT(fcntl(dup_fd.get(), F_GETFD), - SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST(FcntlTest, ClearCloExec) { - // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set. - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC)); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - - // Clear the FD_CLOEXEC flag. - ASSERT_THAT(fcntl(fd.get(), F_SETFD, 0), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, IndependentDescriptorFlags) { - // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set. - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - - // Duplicate the descriptor. Ensure that it also doesn't have FD_CLOEXEC. - FileDescriptor newfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - ASSERT_THAT(fcntl(newfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); - - // Set FD_CLOEXEC on the first FD. - ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - - // Ensure that the second FD is unaffected by the change on the first. - ASSERT_THAT(fcntl(newfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); -} - -// All file description flags passed to open appear in F_GETFL. -TEST(FcntlTest, GetAllFlags) { - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - int flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND; - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), flags)); - - // Linux forces O_LARGEFILE on all 64-bit kernels and gVisor's is 64-bit. - int expected = flags | kOLargeFile; - - int rflags; - EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(rflags, expected); -} - -// When O_PATH is specified in flags, flag bits other than O_CLOEXEC, -// O_DIRECTORY, and O_NOFOLLOW are ignored. -TEST(FcntlTest, GetOpathFlag) { - SKIP_IF(IsRunningWithVFS1()); - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - int flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND | O_PATH | - O_NOFOLLOW | O_DIRECTORY; - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), flags)); - - int expected = O_PATH | O_NOFOLLOW | O_DIRECTORY; - - int rflags; - EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(rflags, expected); -} - -TEST(FcntlTest, SetFlags) { - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), 0)); - - int const flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND; - EXPECT_THAT(fcntl(fd.get(), F_SETFL, flags), SyscallSucceeds()); - - // Can't set O_RDWR or O_SYNC. - // Linux forces O_LARGEFILE on all 64-bit kernels and gVisor's is 64-bit. - int expected = O_DIRECT | O_NONBLOCK | O_APPEND | kOLargeFile; - - int rflags; - EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(rflags, expected); -} - -void TestLock(int fd, short lock_type = F_RDLCK) { // NOLINT, type in flock - struct flock fl; - fl.l_type = lock_type; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // len 0 locks all bytes despite how large the file grows. - fl.l_len = 0; - EXPECT_THAT(fcntl(fd, F_SETLK, &fl), SyscallSucceeds()); -} - -void TestLockBadFD(int fd, - short lock_type = F_RDLCK) { // NOLINT, type in flock - struct flock fl; - fl.l_type = lock_type; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // len 0 locks all bytes despite how large the file grows. - fl.l_len = 0; - EXPECT_THAT(fcntl(fd, F_SETLK, &fl), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(FcntlLockTest, SetLockBadFd) { TestLockBadFD(-1); } - -TEST_F(FcntlLockTest, SetLockDir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0000)); - TestLock(fd.get()); -} - -TEST_F(FcntlLockTest, SetLockSymlink) { - // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH - // is supported. - SKIP_IF(IsRunningOnGvisor()); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto symlink = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path())); - - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(symlink.path(), O_RDONLY | O_PATH, 0000)); - TestLockBadFD(fd.get()); -} - -TEST_F(FcntlLockTest, SetLockProc) { - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/status", O_RDONLY, 0000)); - TestLock(fd.get()); -} - -TEST_F(FcntlLockTest, SetLockPipe) { - SKIP_IF(IsRunningWithVFS1()); - - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - TestLock(fds[0]); - TestLockBadFD(fds[0], F_WRLCK); - - TestLock(fds[1], F_WRLCK); - TestLockBadFD(fds[1]); - - EXPECT_THAT(close(fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(fds[1]), SyscallSucceeds()); -} - -TEST_F(FcntlLockTest, SetLockSocket) { - SKIP_IF(IsRunningWithVFS1()); - - int sock = socket(AF_UNIX, SOCK_STREAM, 0); - ASSERT_THAT(sock, SyscallSucceeds()); - - struct sockaddr_un addr = - ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true /* abstract */, AF_UNIX)); - ASSERT_THAT( - bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceeds()); - - TestLock(sock); - EXPECT_THAT(close(sock), SyscallSucceeds()); -} - -TEST_F(FcntlLockTest, SetLockBadOpenFlagsWrite) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY, 0666)); - - struct flock fl0; - fl0.l_type = F_WRLCK; - fl0.l_whence = SEEK_SET; - fl0.l_start = 0; - fl0.l_len = 0; // Lock all file - - // Expect that setting a write lock using a read only file descriptor - // won't work. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(FcntlLockTest, SetLockBadOpenFlagsRead) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY, 0666)); - - struct flock fl1; - fl1.l_type = F_RDLCK; - fl1.l_whence = SEEK_SET; - fl1.l_start = 0; - // Same as SetLockBadFd. - fl1.l_len = 0; - - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(FcntlLockTest, SetLockWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - struct flock fl0; - fl0.l_type = F_WRLCK; - fl0.l_whence = SEEK_SET; - fl0.l_start = 0; - fl0.l_len = 0; // Lock all file - - // Expect that setting a write lock using a Opath file descriptor - // won't work. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(FcntlLockTest, SetLockUnlockOnNothing) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_UNLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); -} - -TEST_F(FcntlLockTest, SetWriteLockSingleProc) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd0 = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - EXPECT_THAT(fcntl(fd0.get(), F_SETLK, &fl), SyscallSucceeds()); - // Expect to be able to take the same lock on the same fd no problem. - EXPECT_THAT(fcntl(fd0.get(), F_SETLK, &fl), SyscallSucceeds()); - - FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - // Expect to be able to take the same lock from a different fd but for - // the same process. - EXPECT_THAT(fcntl(fd1.get(), F_SETLK, &fl), SyscallSucceeds()); -} - -TEST_F(FcntlLockTest, SetReadLockMultiProc) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // spawn a child process to take a read lock on the same file. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), false /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetReadThenWriteLockMultiProc) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Assert that another process trying to lock on the same file will fail - // with EAGAIN. It's important that we keep the fd above open so that - // that the other process will contend with the lock. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; - - // Close the fd: we want to test that another process can acquire the - // lock after this point. - fd.reset(); - // Assert that another process can now acquire the lock. - - child_pid = 0; - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetWriteThenReadLockMultiProc) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - // Same as SetReadThenWriteLockMultiProc. - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - // Same as SetReadThenWriteLockMultiProc. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Same as SetReadThenWriteLockMultiProc. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), false /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; - - // Same as SetReadThenWriteLockMultiProc. - fd.reset(); // Close the fd. - - // Same as SetReadThenWriteLockMultiProc. - child_pid = 0; - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), false /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetWriteLockMultiProc) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - // Same as SetReadThenWriteLockMultiProc. - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - // Same as SetReadWriteLockMultiProc. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Same as SetReadWriteLockMultiProc. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; - - fd.reset(); // Close the FD. - // Same as SetReadWriteLockMultiProc. - child_pid = 0; - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetLockIsRegional) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 4096; - - // Same as SetReadWriteLockMultiProc. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Same as SetReadWriteLockMultiProc. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_len, 0, &child_pid)); - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetLockUpgradeDowngrade) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - // Same as SetReadWriteLockMultiProc. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Upgrade to a write lock. This will prevent anyone else from taking - // the lock. - fl.l_type = F_WRLCK; - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Same as SetReadWriteLockMultiProc., - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), false /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; - - // Downgrade back to a read lock. - fl.l_type = F_RDLCK; - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Do the same stint as before, but this time it should succeed. - child_pid = 0; - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), false /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetLockDroppedOnClose) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - // While somewhat surprising, obtaining another fd to the same file and - // then closing it in this process drops *all* locks. - FileDescriptor other_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - // Same as SetReadThenWriteLockMultiProc. - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - // Same as SetReadWriteLockMultiProc. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - other_fd.reset(); // Close. - - // Expect to be able to get the lock, given that the close above dropped it. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(file.path(), true /* write lock */, - false /* nonblocking */, false /* no eintr retry */, - -1 /* no socket fd */, fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetLockUnlock) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - // Setup two regional locks with different permissions. - struct flock fl0; - fl0.l_type = F_WRLCK; - fl0.l_whence = SEEK_SET; - fl0.l_start = 0; - fl0.l_len = 4096; - - struct flock fl1; - fl1.l_type = F_RDLCK; - fl1.l_whence = SEEK_SET; - fl1.l_start = 4096; - // Same as SetLockBadFd. - fl1.l_len = 0; - - // Set both region locks. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallSucceeds()); - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallSucceeds()); - - // Another process should fail to take a read lock on the entire file - // due to the regional write lock. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), false /* write lock */, false /* nonblocking */, - false /* no eintr retry */, -1 /* no socket fd */, 0, 0, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; - - // Then only unlock the writable one. This should ensure that other - // processes can take any read lock that it wants. - fl0.l_type = F_UNLCK; - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallSucceeds()); - - // Another process should now succeed to get a read lock on the entire file. - child_pid = 0; - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), false /* write lock */, false /* nonblocking */, - false /* no eintr retry */, -1 /* no socket fd */, 0, 0, &child_pid)); - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST_F(FcntlLockTest, SetLockAcrossRename) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - // Setup two regional locks with different permissions. - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - // Same as SetLockBadFd. - fl.l_len = 0; - - // Set the region lock. - EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - // Rename the file to someplace nearby. - std::string const newpath = NewTempAbsPath(); - EXPECT_THAT(rename(file.path().c_str(), newpath.c_str()), SyscallSucceeds()); - - // Another process should fail to take a read lock on the renamed file - // since we still have an open handle to the inode. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - SubprocessLock(newpath, false /* write lock */, false /* nonblocking */, - false /* no eintr retry */, -1 /* no socket fd */, - fl.l_start, fl.l_len, &child_pid)); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == EAGAIN) - << "Exited with code: " << status; -} - -// NOTE: The blocking tests below aren't perfect. It's hard to assert exactly -// what the kernel did while handling a syscall. These tests are timing based -// because there really isn't any other reasonable way to assert that correct -// blocking behavior happened. - -// This test will verify that blocking works as expected when another process -// holds a write lock when obtaining a write lock. This test will hold the lock -// for some amount of time and then wait for the second process to send over the -// socket_fd the amount of time it was blocked for before the lock succeeded. -TEST_F(FcntlLockTest, SetWriteLockThenBlockingWriteLock) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - - // Take the write lock. - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Attempt to take the read lock in a sub process. This will immediately block - // so we will release our lock after some amount of time and then assert the - // amount of time the other process was blocked for. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), true /* write lock */, true /* Blocking Lock */, - true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */, - fl.l_start, fl.l_len, &child_pid)); - - // We will wait kHoldLockForSec before we release our lock allowing the - // subprocess to obtain it. - constexpr absl::Duration kHoldLockFor = absl::Seconds(5); - const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1)); - - absl::SleepFor(kHoldLockFor); - - // Unlock our write lock. - fl.l_type = F_UNLCK; - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Read the blocked time from the subprocess socket. - int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec(); - - // We must have been waiting at least kMinBlockTime. - EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec); - - // The FCNTL write lock must always succeed as it will simply block until it - // can obtain the lock. - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -// This test will verify that blocking works as expected when another process -// holds a read lock when obtaining a write lock. This test will hold the lock -// for some amount of time and then wait for the second process to send over the -// socket_fd the amount of time it was blocked for before the lock succeeded. -TEST_F(FcntlLockTest, SetReadLockThenBlockingWriteLock) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - - // Take the write lock. - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Attempt to take the read lock in a sub process. This will immediately block - // so we will release our lock after some amount of time and then assert the - // amount of time the other process was blocked for. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), true /* write lock */, true /* Blocking Lock */, - true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */, - fl.l_start, fl.l_len, &child_pid)); - - // We will wait kHoldLockForSec before we release our lock allowing the - // subprocess to obtain it. - constexpr absl::Duration kHoldLockFor = absl::Seconds(5); - - const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1)); - - absl::SleepFor(kHoldLockFor); - - // Unlock our READ lock. - fl.l_type = F_UNLCK; - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Read the blocked time from the subprocess socket. - int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec(); - - // We must have been waiting at least kMinBlockTime. - EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec); - - // The FCNTL write lock must always succeed as it will simply block until it - // can obtain the lock. - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -// This test will veirfy that blocking works as expected when another process -// holds a write lock when obtaining a read lock. This test will hold the lock -// for some amount of time and then wait for the second process to send over the -// socket_fd the amount of time it was blocked for before the lock succeeded. -TEST_F(FcntlLockTest, SetWriteLockThenBlockingReadLock) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - - // Take the write lock. - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Attempt to take the read lock in a sub process. This will immediately block - // so we will release our lock after some amount of time and then assert the - // amount of time the other process was blocked for. - pid_t child_pid = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), false /* read lock */, true /* Blocking Lock */, - true /* Retry on EINTR */, fds_[1] /* Socket fd for timing information */, - fl.l_start, fl.l_len, &child_pid)); - - // We will wait kHoldLockForSec before we release our lock allowing the - // subprocess to obtain it. - constexpr absl::Duration kHoldLockFor = absl::Seconds(5); - - const int64_t kMinBlockTimeUsec = absl::ToInt64Microseconds(absl::Seconds(1)); - - absl::SleepFor(kHoldLockFor); - - // Unlock our write lock. - fl.l_type = F_UNLCK; - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Read the blocked time from the subprocess socket. - int64_t subprocess_blocked_time_usec = GetSubprocessFcntlTimeInUsec(); - - // We must have been waiting at least kMinBlockTime. - EXPECT_GT(subprocess_blocked_time_usec, kMinBlockTimeUsec); - - // The FCNTL read lock must always succeed as it will simply block until it - // can obtain the lock. - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -// This test will verify that when one process only holds a read lock that -// another will not block while obtaining a read lock when F_SETLKW is used. -TEST_F(FcntlLockTest, SetReadLockThenBlockingReadLock) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - - // Take the READ lock. - ASSERT_THAT(fcntl(fd.get(), F_SETLKW, &fl), SyscallSucceeds()); - - // Attempt to take the read lock in a sub process. Since multiple processes - // can hold a read lock this should immediately return without blocking - // even though we used F_SETLKW in the subprocess. - pid_t child_pid = 0; - auto sp = ASSERT_NO_ERRNO_AND_VALUE(SubprocessLock( - file.path(), false /* read lock */, true /* Blocking Lock */, - true /* Retry on EINTR */, -1 /* No fd, should not block */, fl.l_start, - fl.l_len, &child_pid)); - - // We never release the lock and the subprocess should still obtain it without - // blocking for any period of time. - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -TEST(FcntlTest, GetO_ASYNC) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int flag_fl = -1; - ASSERT_THAT(flag_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(flag_fl & O_ASYNC, 0); - - int flag_fd = -1; - ASSERT_THAT(flag_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds()); - EXPECT_EQ(flag_fd & O_ASYNC, 0); -} - -TEST(FcntlTest, SetFlO_ASYNC) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int before_fl = -1; - ASSERT_THAT(before_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - - int before_fd = -1; - ASSERT_THAT(before_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds()); - - ASSERT_THAT(fcntl(s.get(), F_SETFL, before_fl | O_ASYNC), SyscallSucceeds()); - - int after_fl = -1; - ASSERT_THAT(after_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(after_fl, before_fl | O_ASYNC); - - int after_fd = -1; - ASSERT_THAT(after_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds()); - EXPECT_EQ(after_fd, before_fd); -} - -TEST(FcntlTest, SetFdO_ASYNC) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int before_fl = -1; - ASSERT_THAT(before_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - - int before_fd = -1; - ASSERT_THAT(before_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds()); - - ASSERT_THAT(fcntl(s.get(), F_SETFD, before_fd | O_ASYNC), SyscallSucceeds()); - - int after_fl = -1; - ASSERT_THAT(after_fl = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(after_fl, before_fl); - - int after_fd = -1; - ASSERT_THAT(after_fd = fcntl(s.get(), F_GETFD), SyscallSucceeds()); - EXPECT_EQ(after_fd, before_fd); -} - -TEST(FcntlTest, DupAfterO_ASYNC) { - FileDescriptor s1 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int before = -1; - ASSERT_THAT(before = fcntl(s1.get(), F_GETFL), SyscallSucceeds()); - - ASSERT_THAT(fcntl(s1.get(), F_SETFL, before | O_ASYNC), SyscallSucceeds()); - - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(s1.Dup()); - - int after = -1; - ASSERT_THAT(after = fcntl(fd2.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(after & O_ASYNC, O_ASYNC); -} - -TEST(FcntlTest, GetOwnNone) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - // Use the raw syscall because the glibc wrapper may convert F_{GET,SET}OWN - // into F_{GET,SET}OWN_EX. - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, GetOwnExNone) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, SetOwnInvalidPid) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 12345678), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(FcntlTest, SetOwnInvalidPgrp) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -12345678), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(FcntlTest, SetOwnPid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - pid_t pid; - EXPECT_THAT(pid = getpid(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(pid)); -} - -TEST(FcntlTest, SetOwnPgrp) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - pid_t pgid; - EXPECT_THAT(pgid = getpgrp(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -pgid), - SyscallSucceedsWithValue(0)); - - // Verify with F_GETOWN_EX; using F_GETOWN on Linux may incorrectly treat the - // negative return value as an error, converting the return value to -1 and - // setting errno accordingly. - f_owner_ex got_owner = {}; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(got_owner.type, F_OWNER_PGRP); - EXPECT_EQ(got_owner.pid, pgid); -} - -TEST(FcntlTest, SetOwnUnset) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - // Set and unset pid. - pid_t pid; - EXPECT_THAT(pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 0), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(0)); - - // Set and unset pgid. - pid_t pgid; - EXPECT_THAT(pgid = getpgrp(), SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -pgid), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 0), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(0)); -} - -// F_SETOWN flips the sign of negative values, an operation that is guarded -// against overflow. -TEST(FcntlTest, SetOwnOverflow) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, INT_MIN), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(FcntlTest, SetOwnExInvalidType) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = __pid_type(-1); - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(FcntlTest, SetOwnExInvalidTid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = F_OWNER_TID; - owner.pid = -1; - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(FcntlTest, SetOwnExInvalidPid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = F_OWNER_PID; - owner.pid = -1; - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(FcntlTest, SetOwnExInvalidPgrp) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = F_OWNER_PGRP; - owner.pid = -1; - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(FcntlTest, SetOwnExTid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = F_OWNER_TID; - EXPECT_THAT(owner.pid = syscall(__NR_gettid), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(owner.pid)); -} - -TEST(FcntlTest, SetOwnExPid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex owner = {}; - owner.type = F_OWNER_PID; - EXPECT_THAT(owner.pid = getpid(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(owner.pid)); -} - -TEST(FcntlTest, SetOwnExPgrp) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex set_owner = {}; - set_owner.type = F_OWNER_PGRP; - EXPECT_THAT(set_owner.pid = getpgrp(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner), - SyscallSucceedsWithValue(0)); - - // Verify with F_GETOWN_EX; using F_GETOWN on Linux may incorrectly treat the - // negative return value as an error, converting the return value to -1 and - // setting errno accordingly. - f_owner_ex got_owner = {}; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(got_owner.type, set_owner.type); - EXPECT_EQ(got_owner.pid, set_owner.pid); -} - -TEST(FcntlTest, SetOwnExUnset) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - // Set and unset pid. - f_owner_ex owner = {}; - owner.type = F_OWNER_PID; - EXPECT_THAT(owner.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - owner.pid = 0; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(0)); - - // Set and unset pgid. - owner.type = F_OWNER_PGRP; - EXPECT_THAT(owner.pid = getpgrp(), SyscallSucceeds()); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - owner.pid = 0; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, GetOwnExTid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex set_owner = {}; - set_owner.type = F_OWNER_TID; - EXPECT_THAT(set_owner.pid = syscall(__NR_gettid), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner), - SyscallSucceedsWithValue(0)); - - f_owner_ex got_owner = {}; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(got_owner.type, set_owner.type); - EXPECT_EQ(got_owner.pid, set_owner.pid); -} - -TEST(FcntlTest, GetOwnExPid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex set_owner = {}; - set_owner.type = F_OWNER_PID; - EXPECT_THAT(set_owner.pid = getpid(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner), - SyscallSucceedsWithValue(0)); - - f_owner_ex got_owner = {}; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(got_owner.type, set_owner.type); - EXPECT_EQ(got_owner.pid, set_owner.pid); -} - -TEST(FcntlTest, GetOwnExPgrp) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - f_owner_ex set_owner = {}; - set_owner.type = F_OWNER_PGRP; - EXPECT_THAT(set_owner.pid = getpgrp(), SyscallSucceeds()); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner), - SyscallSucceedsWithValue(0)); - - f_owner_ex got_owner = {}; - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(got_owner.type, set_owner.type); - EXPECT_EQ(got_owner.pid, set_owner.pid); -} - -TEST(FcntlTest, SetSig) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(SIGUSR1)); -} - -TEST(FcntlTest, SetSigDefaultsToZero) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - // Defaults to returning the zero value, indicating default behavior (SIGIO). - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, SetSigToDefault) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGIO), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(SIGIO)); - - // Can be reset to the default behavior. - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, 0), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, SetSigInvalid) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(0)); -} - -TEST(FcntlTest, SetSigInvalidDoesNotResetPreviousChoice) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), - SyscallSucceedsWithValue(SIGUSR1)); -} - -TEST_F(FcntlSignalTest, SetSigDefault) { - const auto signal_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); - RegisterFD(pipe_read_fd_, 0); // Zero = default behavior - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - signals_received_.pop_front(); - EXPECT_EQ(sig.num, SIGIO); - EXPECT_EQ(sig.info.si_signo, SIGIO); - // siginfo contents is undefined in this case. -} - -TEST_F(FcntlSignalTest, SetSigCustom) { - const auto signal_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - RegisterFD(pipe_read_fd_, SIGUSR1); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - signals_received_.pop_front(); - EXPECT_EQ(sig.num, SIGUSR1); - EXPECT_EQ(sig.info.si_signo, SIGUSR1); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigUnregisterStillGetsSigio) { - const auto sigio_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - RegisterFD(pipe_read_fd_, SIGUSR1); - RegisterFD(pipe_read_fd_, 0); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - signals_received_.pop_front(); - EXPECT_EQ(sig.num, SIGIO); - // siginfo contents is undefined in this case. -} - -TEST_F(FcntlSignalTest, SetSigWithSigioStillGetsSiginfo) { - const auto signal_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); - RegisterFD(pipe_read_fd_, SIGIO); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - EXPECT_EQ(sig.num, SIGIO); - EXPECT_EQ(sig.info.si_signo, SIGIO); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupThenCloseOld) { - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - FlushAndCloseFD(pipe_read_fd_); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the **old** FD (even though it is closed). - EXPECT_EQ(sig.num, SIGUSR1); - EXPECT_EQ(sig.info.si_signo, SIGUSR1); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupThenCloseNew) { - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - FlushAndCloseFD(pipe_read_fd_dup_); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the old FD. - EXPECT_EQ(sig.num, SIGUSR1); - EXPECT_EQ(sig.info.si_signo, SIGUSR1); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupOldRegistered) { - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the old FD. - EXPECT_EQ(sig.num, SIGUSR1); - EXPECT_EQ(sig.info.si_signo, SIGUSR1); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupNewRegistered) { - const auto sigusr2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); - DupReadFD(); - RegisterFD(pipe_read_fd_dup_, SIGUSR2); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the new FD. - EXPECT_EQ(sig.num, SIGUSR2); - EXPECT_EQ(sig.info.si_signo, SIGUSR2); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_dup_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupBothRegistered) { - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - const auto sigusr2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - RegisterFD(pipe_read_fd_dup_, SIGUSR2); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the **new** signal number, but the **old** FD. - EXPECT_EQ(sig.num, SIGUSR2); - EXPECT_EQ(sig.info.si_signo, SIGUSR2); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupBothRegisteredAfterDup) { - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - const auto sigusr2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); - DupReadFD(); - RegisterFD(pipe_read_fd_, SIGUSR1); - RegisterFD(pipe_read_fd_dup_, SIGUSR2); - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with the **new** signal number, but the **old** FD. - EXPECT_EQ(sig.num, SIGUSR2); - EXPECT_EQ(sig.info.si_signo, SIGUSR2); - EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); - EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); -} - -TEST_F(FcntlSignalTest, SetSigDupUnregisterOld) { - const auto sigio_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - const auto sigusr2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - RegisterFD(pipe_read_fd_dup_, SIGUSR2); - RegisterFD(pipe_read_fd_, 0); // Should go back to SIGIO behavior. - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with SIGIO. - EXPECT_EQ(sig.num, SIGIO); - // siginfo is undefined in this case. -} - -TEST_F(FcntlSignalTest, SetSigDupUnregisterNew) { - const auto sigio_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); - const auto sigusr1_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); - const auto sigusr2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); - RegisterFD(pipe_read_fd_, SIGUSR1); - DupReadFD(); - RegisterFD(pipe_read_fd_dup_, SIGUSR2); - RegisterFD(pipe_read_fd_dup_, 0); // Should go back to SIGIO behavior. - GenerateIOEvent(); - WaitForSignalDelivery(absl::Seconds(1)); - ASSERT_EQ(num_signals_received_, 1); - SignalDelivery sig = signals_received_.front(); - // We get a signal with SIGIO. - EXPECT_EQ(sig.num, SIGIO); - // siginfo is undefined in this case. -} - -// Make sure that making multiple concurrent changes to async signal generation -// does not cause any race issues. -TEST(FcntlTest, SetFlSetOwnSetSigDoNotRace) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - pid_t pid; - EXPECT_THAT(pid = getpid(), SyscallSucceeds()); - - constexpr absl::Duration runtime = absl::Milliseconds(300); - auto set_async = [&s, &runtime] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, O_ASYNC), - SyscallSucceeds()); - sched_yield(); - } - }; - auto reset_async = [&s, &runtime] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, 0), SyscallSucceeds()); - sched_yield(); - } - }; - auto set_own = [&s, &pid, &runtime] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid), - SyscallSucceeds()); - sched_yield(); - } - }; - auto set_sig = [&s, &runtime] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), - SyscallSucceeds()); - sched_yield(); - } - }; - - std::list<ScopedThread> threads; - for (int i = 0; i < 10; i++) { - threads.emplace_back(set_async); - threads.emplace_back(reset_async); - threads.emplace_back(set_own); - threads.emplace_back(set_sig); - } -} - -TEST_F(FcntlLockTest, GetLockOnNothing) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds()); - ASSERT_TRUE(fl.l_type == F_UNLCK); -} - -TEST_F(FcntlLockTest, GetLockOnLockSameProcess) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds()); - ASSERT_TRUE(fl.l_type == F_UNLCK); - - fl.l_type = F_WRLCK; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds()); - ASSERT_TRUE(fl.l_type == F_UNLCK); -} - -TEST_F(FcntlLockTest, GetReadLockOnReadLock) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - pid_t child_pid = fork(); - if (child_pid == 0) { - TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0); - TEST_CHECK(fl.l_type == F_UNLCK); - _exit(0); - } - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -TEST_F(FcntlLockTest, GetReadLockOnWriteLock) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - fl.l_type = F_RDLCK; - pid_t child_pid = fork(); - if (child_pid == 0) { - TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0); - TEST_CHECK(fl.l_type == F_WRLCK); - TEST_CHECK(fl.l_pid == getppid()); - _exit(0); - } - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -TEST_F(FcntlLockTest, GetWriteLockOnReadLock) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_RDLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - fl.l_type = F_WRLCK; - pid_t child_pid = fork(); - if (child_pid == 0) { - TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0); - TEST_CHECK(fl.l_type == F_RDLCK); - TEST_CHECK(fl.l_pid == getppid()); - _exit(0); - } - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -TEST_F(FcntlLockTest, GetWriteLockOnWriteLock) { - SKIP_IF(IsRunningWithVFS1()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - pid_t child_pid = fork(); - if (child_pid == 0) { - TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0); - TEST_CHECK(fl.l_type == F_WRLCK); - TEST_CHECK(fl.l_pid == getppid()); - _exit(0); - } - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -// Tests that the pid returned from F_GETLK is relative to the caller's PID -// namespace. -TEST_F(FcntlLockTest, GetLockRespectsPIDNamespace) { - SKIP_IF(IsRunningWithVFS1()); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - std::string filename = file.path(); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR, 0666)); - - // Lock in the parent process. - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds()); - - auto child_getlk = [](void* filename) { - int fd = open((char*)filename, O_RDWR, 0666); - TEST_CHECK(fd >= 0); - - struct flock fl; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 40; - TEST_CHECK(fcntl(fd, F_GETLK, &fl) >= 0); - TEST_CHECK(fl.l_type == F_WRLCK); - // Parent PID should be 0 in the child PID namespace. - TEST_CHECK(fl.l_pid == 0); - close(fd); - return 0; - }; - - // Set up child process in a new PID namespace. - constexpr int kStackSize = 4096; - Mapping stack = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); - pid_t child_pid; - ASSERT_THAT( - child_pid = clone(child_getlk, (char*)stack.ptr() + stack.len(), - CLONE_NEWPID | SIGCHLD, (void*)filename.c_str()), - SyscallSucceeds()); - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int set_lock() { - const std::string set_lock_on = absl::GetFlag(FLAGS_child_set_lock_on); - int socket_fd = absl::GetFlag(FLAGS_socket_fd); - int fd = open(set_lock_on.c_str(), O_RDWR, 0666); - if (fd == -1 && errno != 0) { - int err = errno; - std::cerr << "CHILD open " << set_lock_on << " failed: " << err - << std::endl; - return err; - } - - struct flock fl; - if (absl::GetFlag(FLAGS_child_set_lock_write)) { - fl.l_type = F_WRLCK; - } else { - fl.l_type = F_RDLCK; - } - fl.l_whence = SEEK_SET; - fl.l_start = absl::GetFlag(FLAGS_child_set_lock_start); - fl.l_len = absl::GetFlag(FLAGS_child_set_lock_len); - - // Test the fcntl. - int err = 0; - int ret = 0; - - gvisor::testing::MonotonicTimer timer; - timer.Start(); - do { - ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl); - } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR); - auto usec = absl::ToInt64Microseconds(timer.Duration()); - - if (ret == -1 && errno != 0) { - err = errno; - std::cerr << "CHILD lock " << set_lock_on << " failed " << err << std::endl; - } - - // If there is a socket fd let's send back the time in microseconds it took - // to execute this syscall. - if (socket_fd != -1) { - gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec), - sizeof(usec)); - close(socket_fd); - } - - close(fd); - return err; -} - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (!absl::GetFlag(FLAGS_child_set_lock_on).empty()) { - exit(set_lock()); - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/file_base.h b/test/syscalls/linux/file_base.h deleted file mode 100644 index fb418e052..000000000 --- a/test/syscalls/linux/file_base.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_FILE_BASE_H_ -#define GVISOR_TEST_SYSCALLS_FILE_BASE_H_ - -#include <arpa/inet.h> -#include <errno.h> -#include <fcntl.h> -#include <netinet/in.h> -#include <stddef.h> -#include <stdio.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <cstring> -#include <string> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -class FileTest : public ::testing::Test { - public: - void SetUp() override { - test_pipe_[0] = -1; - test_pipe_[1] = -1; - - test_file_name_ = NewTempAbsPath(); - test_file_fd_ = ASSERT_NO_ERRNO_AND_VALUE( - Open(test_file_name_, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); - - ASSERT_THAT(pipe(test_pipe_), SyscallSucceeds()); - ASSERT_THAT(fcntl(test_pipe_[0], F_SETFL, O_NONBLOCK), SyscallSucceeds()); - } - - // CloseFile will allow the test to manually close the file descriptor. - void CloseFile() { test_file_fd_.reset(); } - - // UnlinkFile will allow the test to manually unlink the file. - void UnlinkFile() { - if (!test_file_name_.empty()) { - EXPECT_THAT(unlink(test_file_name_.c_str()), SyscallSucceeds()); - test_file_name_.clear(); - } - } - - // ClosePipes will allow the test to manually close the pipes. - void ClosePipes() { - if (test_pipe_[0] > 0) { - EXPECT_THAT(close(test_pipe_[0]), SyscallSucceeds()); - } - - if (test_pipe_[1] > 0) { - EXPECT_THAT(close(test_pipe_[1]), SyscallSucceeds()); - } - - test_pipe_[0] = -1; - test_pipe_[1] = -1; - } - - void TearDown() override { - CloseFile(); - UnlinkFile(); - ClosePipes(); - } - - protected: - std::string test_file_name_; - FileDescriptor test_file_fd_; - - int test_pipe_[2]; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_FILE_BASE_H_ diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc deleted file mode 100644 index b286e84fe..000000000 --- a/test/syscalls/linux/flock.cc +++ /dev/null @@ -1,716 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sys/file.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/file_base.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class FlockTest : public FileTest {}; - -TEST_F(FlockTest, InvalidOpCombinations) { - // The operation cannot be both exclusive and shared. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_SH | LOCK_NB), - SyscallFailsWithErrno(EINVAL)); - - // Locking and Unlocking doesn't make sense. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_UN | LOCK_NB), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_UN | LOCK_NB), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(FlockTest, NoOperationSpecified) { - // Not specifying an operation is invalid. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(FlockTest, TestSimpleExLock) { - // Test that we can obtain an exclusive lock (no other holders) - // and that we can unlock it. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestSimpleShLock) { - // Test that we can obtain a shared lock (no other holders) - // and that we can unlock it. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestLockableAnyMode) { - // flock(2): A shared or exclusive lock can be placed on a file - // regardless of the mode in which the file was opened. - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(test_file_name_, O_RDONLY)); // open read only to test - - // Mode shouldn't prevent us from taking an exclusive lock. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Unlock - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestUnlockWithNoHolders) { - // Test that unlocking when no one holds a lock succeeeds. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestRepeatedExLockingBySameHolder) { - // Test that repeated locking by the same holder for the - // same type of lock works correctly. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestRepeatedExLockingSingleUnlock) { - // Test that repeated locking by the same holder for the - // same type of lock works correctly and that a single unlock is required. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - - // Should be unlocked at this point - ASSERT_THAT(flock(fd.get(), LOCK_NB | LOCK_EX), SyscallSucceedsWithValue(0)); - - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestRepeatedShLockingBySameHolder) { - // Test that repeated locking by the same holder for the - // same type of lock works correctly. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestSingleHolderUpgrade) { - // Test that a shared lock is upgradable when no one else holds a lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_SH), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_NB | LOCK_EX), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestSingleHolderDowngrade) { - // Test single holder lock downgrade case. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestMultipleShared) { - // This is a simple test to verify that multiple independent shared - // locks will be granted. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // A shared lock should be granted as there only exists other shared locks. - ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Unlock both. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -/* - * flock(2): If a process uses open(2) (or similar) to obtain more than one - * descriptor for the same file, these descriptors are treated - * independently by flock(). An attempt to lock the file using one of - * these file descriptors may be denied by a lock that the calling process - * has already placed via another descriptor. - */ -TEST_F(FlockTest, TestMultipleHolderSharedExclusive) { - // This test will verify that an exclusive lock will not be granted - // while a shared is held. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Verify We're unable to get an exlcusive lock via the second FD. - // because someone is holding a shared lock. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Unlock - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestSharedLockFailExclusiveHolderNonblocking) { - // This test will verify that a shared lock is denied while - // someone holds an exclusive lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Verify we're unable to get an shared lock via the second FD. - // because someone is holding an exclusive lock. - ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Unlock - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -void trivial_handler(int signum) {} - -TEST_F(FlockTest, TestSharedLockFailExclusiveHolderBlocking_NoRandomSave) { - const DisableSave ds; // Timing-related. - - // This test will verify that a shared lock is denied while - // someone holds an exclusive lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Make sure that a blocking flock() call will return EINTR when interrupted - // by a signal. Create a timer that will go off while blocking on flock(), and - // register the corresponding signal handler. - auto timer = ASSERT_NO_ERRNO_AND_VALUE( - TimerCreate(CLOCK_MONOTONIC, sigevent_t{ - .sigev_signo = SIGALRM, - .sigev_notify = SIGEV_SIGNAL, - })); - - struct sigaction act = {}; - act.sa_handler = trivial_handler; - ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - - // Now that the signal handler is registered, set the timer. Set an interval - // so that it's ok if the timer goes off before we call flock. - ASSERT_NO_ERRNO( - timer.Set(0, itimerspec{ - .it_interval = absl::ToTimespec(absl::Milliseconds(10)), - .it_value = absl::ToTimespec(absl::Milliseconds(10)), - })); - - ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallFailsWithErrno(EINTR)); - timer.reset(); - - // Unlock - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderNonblocking) { - // This test will verify that an exclusive lock is denied while - // someone already holds an exclsuive lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Verify we're unable to get an exclusive lock via the second FD - // because someone is already holding an exclusive lock. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Unlock - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderBlocking_NoRandomSave) { - const DisableSave ds; // Timing-related. - - // This test will verify that an exclusive lock is denied while - // someone already holds an exclsuive lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Make sure that a blocking flock() call will return EINTR when interrupted - // by a signal. Create a timer that will go off while blocking on flock(), and - // register the corresponding signal handler. - auto timer = ASSERT_NO_ERRNO_AND_VALUE( - TimerCreate(CLOCK_MONOTONIC, sigevent_t{ - .sigev_signo = SIGALRM, - .sigev_notify = SIGEV_SIGNAL, - })); - - struct sigaction act = {}; - act.sa_handler = trivial_handler; - ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - - // Now that the signal handler is registered, set the timer. Set an interval - // so that it's ok if the timer goes off before we call flock. - ASSERT_NO_ERRNO( - timer.Set(0, itimerspec{ - .it_interval = absl::ToTimespec(absl::Milliseconds(10)), - .it_value = absl::ToTimespec(absl::Milliseconds(10)), - })); - - ASSERT_THAT(flock(fd.get(), LOCK_EX), SyscallFailsWithErrno(EINTR)); - timer.reset(); - - // Unlock - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestMultipleHolderSharedExclusiveUpgrade) { - // This test will verify that we cannot obtain an exclusive lock while - // a shared lock is held by another descriptor, then verify that an upgrade - // is possible on a shared lock once all other shared locks have closed. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Verify we're unable to get an exclusive lock via the second FD because - // a shared lock is held. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Verify that we can get a shared lock via the second descriptor instead - ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Unlock the first and there will only be one shared lock remaining. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); - - // Upgrade 2nd fd. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Finally unlock the second - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestMultipleHolderSharedExclusiveDowngrade) { - // This test will verify that a shared lock is not obtainable while an - // exclusive lock is held but that once the first is downgraded that - // the second independent file descriptor can also get a shared lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Verify We're unable to get a shared lock via the second FD because - // an exclusive lock is held. - ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Verify that we can downgrade the first. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - // Now verify that we can obtain a shared lock since the first was downgraded. - ASSERT_THAT(flock(fd.get(), LOCK_SH | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Finally unlock both. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -/* - * flock(2): Locks created by flock() are associated with an open file table - * entry. This means that duplicate file descriptors (created by, for example, - * fork(2) or dup(2)) refer to the same lock, and this lock may be modified or - * released using any of these descriptors. Furthermore, the lock is released - * either by an explicit LOCK_UN operation on any of these duplicate descriptors - * or when all such descriptors have been closed. - */ -TEST_F(FlockTest, TestDupFdUpgrade) { - // This test will verify that a shared lock is upgradeable via a dupped - // file descriptor, if the FD wasn't dupped this would fail. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); - - // Now we should be able to upgrade via the dupped fd. - ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - // Validate unlock via dupped fd. - ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestDupFdDowngrade) { - // This test will verify that a exclusive lock is downgradable via a dupped - // file descriptor, if the FD wasn't dupped this would fail. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); - - // Now we should be able to downgrade via the dupped fd. - ASSERT_THAT(flock(dup_fd.get(), LOCK_SH | LOCK_NB), - SyscallSucceedsWithValue(0)); - - // Validate unlock via dupped fd - ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestDupFdCloseRelease) { - // flock(2): Furthermore, the lock is released either by an explicit LOCK_UN - // operation on any of these duplicate descriptors, or when all such - // descriptors have been closed. - // - // This test will verify that a dupped fd closing will not release the - // underlying lock until all such dupped fds have closed. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); - - // At this point we have ONE exclusive locked referenced by two different fds. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Validate that we cannot get a lock on a new unrelated FD. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Closing the dupped fd shouldn't affect the lock until all are closed. - dup_fd.reset(); // Closed the duped fd. - - // Validate that we still cannot get a lock on a new unrelated FD. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Closing the first fd - CloseFile(); // Will validate the syscall succeeds. - - // Now we should actually be able to get a lock since all fds related to - // the first lock are closed. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Unlock. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestDupFdUnlockRelease) { - /* flock(2): Furthermore, the lock is released either by an explicit LOCK_UN - * operation on any of these duplicate descriptors, or when all such - * descriptors have been closed. - */ - // This test will verify that an explict unlock on a dupped FD will release - // the underlying lock unlike the previous case where close on a dup was - // not enough to release the lock. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_NB), - SyscallSucceedsWithValue(0)); - - const FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); - - // At this point we have ONE exclusive locked referenced by two different fds. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Validate that we cannot get a lock on a new unrelated FD. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Explicitly unlock via the dupped descriptor. - ASSERT_THAT(flock(dup_fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); - - // Validate that we can now get the lock since we explicitly unlocked. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceedsWithValue(0)); - - // Unlock - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -TEST_F(FlockTest, TestDupFdFollowedByLock) { - // This test will verify that taking a lock on a file descriptor that has - // already been dupped means that the lock is shared between both. This is - // slightly different than than duping on an already locked FD. - FileDescriptor dup_fd = ASSERT_NO_ERRNO_AND_VALUE(test_file_fd_.Dup()); - - // Take a lock. - ASSERT_THAT(flock(dup_fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); - - // Now dup_fd and test_file_ should both reference the same lock. - // We shouldn't be able to obtain a lock until both are closed. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Closing the first fd - dup_fd.reset(); // Close the duped fd. - - // Validate that we cannot get a lock yet because the dupped descriptor. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Closing the second fd. - CloseFile(); // CloseFile() will validate the syscall succeeds. - - // Now we should be able to get the lock. - ASSERT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); - - // Unlock. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceedsWithValue(0)); -} - -// NOTE: These blocking tests are not perfect. Unfortunately it's very hard to -// determine if a thread was actually blocked in the kernel so we're forced -// to use timing. -TEST_F(FlockTest, BlockingLockNoBlockingForSharedLocks_NoRandomSave) { - // This test will verify that although LOCK_NB isn't specified - // two different fds can obtain shared locks without blocking. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds()); - - // kHoldLockTime is the amount of time we will hold the lock before releasing. - constexpr absl::Duration kHoldLockTime = absl::Seconds(30); - - const DisableSave ds; // Timing-related. - - // We do this in another thread so we can determine if it was actually - // blocked by timing the amount of time it took for the syscall to complete. - ScopedThread t([&] { - MonotonicTimer timer; - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // Only a single shared lock is held, the lock will be granted immediately. - // This should be granted without any blocking. Don't save here to avoid - // wild discrepencies on timing. - timer.Start(); - ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallSucceeds()); - - // We held the lock for 30 seconds but this thread should not have - // blocked at all so we expect a very small duration on syscall completion. - ASSERT_LT(timer.Duration(), - absl::Seconds(1)); // 1000ms is much less than 30s. - - // We can release our second shared lock - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); - }); - - // Sleep before unlocking. - absl::SleepFor(kHoldLockTime); - - // Release the first shared lock. Don't save in this situation to avoid - // discrepencies in timing. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); -} - -TEST_F(FlockTest, BlockingLockFirstSharedSecondExclusive_NoRandomSave) { - // This test will verify that if someone holds a shared lock any attempt to - // obtain an exclusive lock will result in blocking. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_SH), SyscallSucceeds()); - - // kHoldLockTime is the amount of time we will hold the lock before releasing. - constexpr absl::Duration kHoldLockTime = absl::Seconds(2); - - const DisableSave ds; // Timing-related. - - // We do this in another thread so we can determine if it was actually - // blocked by timing the amount of time it took for the syscall to complete. - ScopedThread t([&] { - MonotonicTimer timer; - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // This exclusive lock should block because someone is already holding a - // shared lock. We don't save here to avoid wild discrepencies on timing. - timer.Start(); - ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds()); - - // We should be blocked, we will expect to be blocked for more than 1.0s. - ASSERT_GT(timer.Duration(), absl::Seconds(1)); - - // We can release our exclusive lock. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); - }); - - // Sleep before unlocking. - absl::SleepFor(kHoldLockTime); - - // Release the shared lock allowing the thread to proceed. - // We don't save here to avoid wild discrepencies in timing. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); -} - -TEST_F(FlockTest, BlockingLockFirstExclusiveSecondShared_NoRandomSave) { - // This test will verify that if someone holds an exclusive lock any attempt - // to obtain a shared lock will result in blocking. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds()); - - // kHoldLockTime is the amount of time we will hold the lock before releasing. - constexpr absl::Duration kHoldLockTime = absl::Seconds(2); - - const DisableSave ds; // Timing-related. - - // We do this in another thread so we can determine if it was actually - // blocked by timing the amount of time it took for the syscall to complete. - ScopedThread t([&] { - MonotonicTimer timer; - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // This shared lock should block because someone is already holding an - // exclusive lock. We don't save here to avoid wild discrepencies on timing. - timer.Start(); - ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_SH), SyscallSucceeds()); - - // We should be blocked, we will expect to be blocked for more than 1.0s. - ASSERT_GT(timer.Duration(), absl::Seconds(1)); - - // We can release our shared lock. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); - }); - - // Sleep before unlocking. - absl::SleepFor(kHoldLockTime); - - // Release the exclusive lock allowing the blocked thread to proceed. - // We don't save here to avoid wild discrepencies in timing. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); -} - -TEST_F(FlockTest, BlockingLockFirstExclusiveSecondExclusive_NoRandomSave) { - // This test will verify that if someone holds an exclusive lock any attempt - // to obtain another exclusive lock will result in blocking. - ASSERT_THAT(flock(test_file_fd_.get(), LOCK_EX), SyscallSucceeds()); - - // kHoldLockTime is the amount of time we will hold the lock before releasing. - constexpr absl::Duration kHoldLockTime = absl::Seconds(2); - - const DisableSave ds; // Timing-related. - - // We do this in another thread so we can determine if it was actually - // blocked by timing the amount of time it took for the syscall to complete. - ScopedThread t([&] { - MonotonicTimer timer; - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - // This exclusive lock should block because someone is already holding an - // exclusive lock. - timer.Start(); - ASSERT_THAT(RetryEINTR(flock)(fd.get(), LOCK_EX), SyscallSucceeds()); - - // We should be blocked, we will expect to be blocked for more than 1.0s. - ASSERT_GT(timer.Duration(), absl::Seconds(1)); - - // We can release our exclusive lock. - ASSERT_THAT(flock(fd.get(), LOCK_UN), SyscallSucceeds()); - }); - - // Sleep before unlocking. - absl::SleepFor(kHoldLockTime); - - // Release the exclusive lock allowing the blocked thread to proceed. - // We don't save to avoid wild discrepencies in timing. - EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds()); -} - -TEST(FlockTestNoFixture, BadFD) { - // EBADF: fd is not an open file descriptor. - ASSERT_THAT(flock(-1, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST(FlockTestNoFixture, FlockDir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0000)); - EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); -} - -TEST(FlockTestNoFixture, FlockSymlink) { - // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH - // is supported. - SKIP_IF(IsRunningOnGvisor()); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto symlink = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path())); - - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(symlink.path(), O_RDONLY | O_PATH, 0000)); - EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EBADF)); -} - -TEST(FlockTestNoFixture, FlockProc) { - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/status", O_RDONLY, 0000)); - EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds()); -} - -TEST(FlockTestNoFixture, FlockPipe) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - EXPECT_THAT(flock(fds[0], LOCK_EX | LOCK_NB), SyscallSucceeds()); - // Check that the pipe was locked above. - EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EAGAIN)); - - EXPECT_THAT(flock(fds[0], LOCK_UN), SyscallSucceeds()); - EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallSucceeds()); - - EXPECT_THAT(close(fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(fds[1]), SyscallSucceeds()); -} - -TEST(FlockTestNoFixture, FlockSocket) { - int sock = socket(AF_UNIX, SOCK_STREAM, 0); - ASSERT_THAT(sock, SyscallSucceeds()); - - struct sockaddr_un addr = - ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true /* abstract */, AF_UNIX)); - ASSERT_THAT( - bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceeds()); - - EXPECT_THAT(flock(sock, LOCK_EX | LOCK_NB), SyscallSucceeds()); - EXPECT_THAT(close(sock), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fork.cc b/test/syscalls/linux/fork.cc deleted file mode 100644 index 853f6231a..000000000 --- a/test/syscalls/linux/fork.cc +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sched.h> -#include <stdlib.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <atomic> -#include <cstdlib> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::Ge; - -class ForkTest : public ::testing::Test { - protected: - // SetUp creates a populated, open file. - void SetUp() override { - // Make a shared mapping. - shared_ = reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, -1, 0)); - ASSERT_NE(reinterpret_cast<void*>(shared_), MAP_FAILED); - - // Make a private mapping. - private_ = - reinterpret_cast<char*>(mmap(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - ASSERT_NE(reinterpret_cast<void*>(private_), MAP_FAILED); - - // Make a pipe. - ASSERT_THAT(pipe(pipes_), SyscallSucceeds()); - } - - // TearDown frees associated resources. - void TearDown() override { - EXPECT_THAT(munmap(shared_, kPageSize), SyscallSucceeds()); - EXPECT_THAT(munmap(private_, kPageSize), SyscallSucceeds()); - EXPECT_THAT(close(pipes_[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipes_[1]), SyscallSucceeds()); - } - - // Fork executes a clone system call. - pid_t Fork() { - pid_t pid = fork(); - MaybeSave(); - TEST_PCHECK_MSG(pid >= 0, "fork failed"); - return pid; - } - - // Wait waits for the given pid and returns the exit status. If the child was - // killed by a signal or an error occurs, then 256+signal is returned. - int Wait(pid_t pid) { - int status; - while (true) { - int rval = wait4(pid, &status, 0, NULL); - if (rval < 0) { - return rval; - } - if (rval != pid) { - continue; - } - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } - if (WIFSIGNALED(status)) { - return 256 + WTERMSIG(status); - } - } - } - - // Exit exits the proccess. - void Exit(int code) { - _exit(code); - - // Should never reach here. Since the exit above failed, we really don't - // have much in the way of options to indicate failure. So we just try to - // log an assertion failure to the logs. The parent process will likely - // fail anyways if exit is not working. - TEST_CHECK_MSG(false, "_exit returned"); - } - - // ReadByte reads a byte from the shared pipe. - char ReadByte() { - char val = -1; - TEST_PCHECK(ReadFd(pipes_[0], &val, 1) == 1); - MaybeSave(); - return val; - } - - // WriteByte writes a byte from the shared pipe. - void WriteByte(char val) { - TEST_PCHECK(WriteFd(pipes_[1], &val, 1) == 1); - MaybeSave(); - } - - // Shared pipe. - int pipes_[2]; - - // Shared mapping (one page). - char* shared_; - - // Private mapping (one page). - char* private_; -}; - -TEST_F(ForkTest, Simple) { - pid_t child = Fork(); - if (child == 0) { - Exit(0); - } - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -TEST_F(ForkTest, ExitCode) { - pid_t child = Fork(); - if (child == 0) { - Exit(123); - } - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(123)); - child = Fork(); - if (child == 0) { - Exit(1); - } - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(1)); -} - -TEST_F(ForkTest, Multi) { - pid_t child1 = Fork(); - if (child1 == 0) { - Exit(0); - } - pid_t child2 = Fork(); - if (child2 == 0) { - Exit(1); - } - EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0)); - EXPECT_THAT(Wait(child2), SyscallSucceedsWithValue(1)); -} - -TEST_F(ForkTest, Pipe) { - pid_t child = Fork(); - if (child == 0) { - WriteByte(1); - Exit(0); - } - EXPECT_EQ(ReadByte(), 1); - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -TEST_F(ForkTest, SharedMapping) { - pid_t child = Fork(); - if (child == 0) { - // Wait for the parent. - ReadByte(); - if (shared_[0] == 1) { - Exit(0); - } - // Failed. - Exit(1); - } - // Change the mapping. - ASSERT_EQ(shared_[0], 0); - shared_[0] = 1; - // Unblock the child. - WriteByte(0); - // Did it work? - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -TEST_F(ForkTest, PrivateMapping) { - pid_t child = Fork(); - if (child == 0) { - // Wait for the parent. - ReadByte(); - if (private_[0] == 0) { - Exit(0); - } - // Failed. - Exit(1); - } - // Change the mapping. - ASSERT_EQ(private_[0], 0); - private_[0] = 1; - // Unblock the child. - WriteByte(0); - // Did it work? - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -// CPUID is x86 specific. -#ifdef __x86_64__ -// Test that cpuid works after a fork. -TEST_F(ForkTest, Cpuid) { - pid_t child = Fork(); - - // We should be able to determine the CPU vendor. - ASSERT_NE(GetCPUVendor(), CPUVendor::kUnknownVendor); - - if (child == 0) { - Exit(0); - } - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} -#endif - -TEST_F(ForkTest, Mmap) { - pid_t child = Fork(); - - if (child == 0) { - void* addr = - mmap(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - MaybeSave(); - Exit(addr == MAP_FAILED); - } - - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -static volatile int alarmed = 0; - -void AlarmHandler(int sig, siginfo_t* info, void* context) { alarmed = 1; } - -TEST_F(ForkTest, Alarm) { - // Setup an alarm handler. - struct sigaction sa; - sa.sa_sigaction = AlarmHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - EXPECT_THAT(sigaction(SIGALRM, &sa, nullptr), SyscallSucceeds()); - - pid_t child = Fork(); - - if (child == 0) { - alarm(1); - sleep(3); - if (!alarmed) { - Exit(1); - } - Exit(0); - } - - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, alarmed); -} - -// Child cannot affect parent private memory. Regression test for b/24137240. -TEST_F(ForkTest, PrivateMemory) { - std::atomic<uint32_t> local(0); - - pid_t child1 = Fork(); - if (child1 == 0) { - local++; - - pid_t child2 = Fork(); - if (child2 == 0) { - local++; - - TEST_CHECK(local.load() == 2); - - Exit(0); - } - - TEST_PCHECK(Wait(child2) == 0); - TEST_CHECK(local.load() == 1); - Exit(0); - } - - EXPECT_THAT(Wait(child1), SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, local.load()); -} - -// Kernel-accessed buffers should remain coherent across COW. -// -// The buffer must be >= usermem.ZeroCopyMinBytes, as UnsafeAccess operates -// differently. Regression test for b/33811887. -TEST_F(ForkTest, COWSegment) { - constexpr int kBufSize = 1024; - char* read_buf = private_; - char* touch = private_ + kPageSize / 2; - - std::string contents(kBufSize, 'a'); - - ScopedThread t([&] { - // Wait to be sure the parent is blocked in read. - absl::SleepFor(absl::Seconds(3)); - - // Fork to mark private pages for COW. - // - // Use fork directly rather than the Fork wrapper to skip the multi-threaded - // check, and limit the child to async-signal-safe functions: - // - // "After a fork() in a multithreaded program, the child can safely call - // only async-signal-safe functions (see signal(7)) until such time as it - // calls execve(2)." - // - // Skip ASSERT in the child, as it isn't async-signal-safe. - pid_t child = fork(); - if (child == 0) { - // Wait to be sure parent touched memory. - sleep(3); - Exit(0); - } - - // Check success only in the parent. - ASSERT_THAT(child, SyscallSucceedsWithValue(Ge(0))); - - // Trigger COW on private page. - *touch = 42; - - // Write to pipe. Parent should still be able to read this. - EXPECT_THAT(WriteFd(pipes_[1], contents.c_str(), kBufSize), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); - }); - - EXPECT_THAT(ReadFd(pipes_[0], read_buf, kBufSize), - SyscallSucceedsWithValue(kBufSize)); - EXPECT_STREQ(contents.c_str(), read_buf); -} - -TEST_F(ForkTest, SigAltStack) { - std::vector<char> stack_mem(SIGSTKSZ); - stack_t stack = {}; - stack.ss_size = SIGSTKSZ; - stack.ss_sp = stack_mem.data(); - ASSERT_THAT(sigaltstack(&stack, nullptr), SyscallSucceeds()); - - pid_t child = Fork(); - - if (child == 0) { - stack_t oss = {}; - TEST_PCHECK(sigaltstack(nullptr, &oss) == 0); - MaybeSave(); - - TEST_CHECK((oss.ss_flags & SS_DISABLE) == 0); - TEST_CHECK(oss.ss_size == SIGSTKSZ); - TEST_CHECK(oss.ss_sp == stack.ss_sp); - - Exit(0); - } - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -TEST_F(ForkTest, Affinity) { - // Make a non-default cpumask. - cpu_set_t parent_mask; - EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask), - SyscallSucceeds()); - // Knock out the lowest bit. - for (unsigned int n = 0; n < CPU_SETSIZE; n++) { - if (CPU_ISSET(n, &parent_mask)) { - CPU_CLR(n, &parent_mask); - break; - } - } - EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &parent_mask), - SyscallSucceeds()); - - pid_t child = Fork(); - if (child == 0) { - cpu_set_t child_mask; - - int ret = sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask); - MaybeSave(); - if (ret < 0) { - Exit(-ret); - } - - TEST_CHECK(CPU_EQUAL(&child_mask, &parent_mask)); - - Exit(0); - } - - EXPECT_THAT(Wait(child), SyscallSucceedsWithValue(0)); -} - -TEST(CloneTest, NewUserNamespacePermitsAllOtherNamespaces) { - // "If CLONE_NEWUSER is specified along with other CLONE_NEW* flags in a - // single clone(2) or unshare(2) call, the user namespace is guaranteed to be - // created first, giving the child (clone(2)) or caller (unshare(2)) - // privileges over the remaining namespaces created by the call. Thus, it is - // possible for an unprivileged caller to specify this combination of flags." - // - user_namespaces(7) - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - Mapping child_stack = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - int child_pid; - // We only test with CLONE_NEWIPC, CLONE_NEWNET, and CLONE_NEWUTS since these - // namespaces were implemented in Linux before user namespaces. - ASSERT_THAT( - child_pid = clone( - +[](void*) { return 0; }, - reinterpret_cast<void*>(child_stack.addr() + kPageSize), - CLONE_NEWUSER | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWUTS | SIGCHLD, - /* arg = */ nullptr), - SyscallSucceeds()); - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status = " << status; -} - -// Clone with CLONE_SETTLS and a non-canonical TLS address is rejected. -TEST(CloneTest, NonCanonicalTLS) { - constexpr uintptr_t kNonCanonical = 1ull << 48; - - // We need a valid address for the stack pointer. We'll never actually execute - // on this. - char stack; - - // The raw system call interface on x86-64 is: - // long clone(unsigned long flags, void *stack, - // int *parent_tid, int *child_tid, - // unsigned long tls); - // - // While on arm64, the order of the last two arguments is reversed: - // long clone(unsigned long flags, void *stack, - // int *parent_tid, unsigned long tls, - // int *child_tid); -#if defined(__x86_64__) - EXPECT_THAT(syscall(__NR_clone, SIGCHLD | CLONE_SETTLS, &stack, nullptr, - nullptr, kNonCanonical), - SyscallFailsWithErrno(EPERM)); -#elif defined(__aarch64__) - EXPECT_THAT(syscall(__NR_clone, SIGCHLD | CLONE_SETTLS, &stack, nullptr, - kNonCanonical, nullptr), - SyscallFailsWithErrno(EPERM)); -#endif -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fpsig_fork.cc b/test/syscalls/linux/fpsig_fork.cc deleted file mode 100644 index c47567b4e..000000000 --- a/test/syscalls/linux/fpsig_fork.cc +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This test verifies that fork(2) in a signal handler will correctly -// restore floating point state after the signal handler returns in both -// the child and parent. -#include <sys/time.h> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#ifdef __x86_64__ -#define GET_XMM(__var, __xmm) \ - asm volatile("movq %%" #__xmm ", %0" : "=r"(__var)) -#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var)) -#define GET_FP0(__var) GET_XMM(__var, xmm0) -#define SET_FP0(__var) SET_XMM(__var, xmm0) -#elif __aarch64__ -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) -#define GET_FPREG(var, regname) \ - asm volatile("str " __stringify(regname) ", %0" : "=m"(var)) -#define SET_FPREG(var, regname) \ - asm volatile("ldr " __stringify(regname) ", %0" : "=m"(var)) -#define GET_FP0(var) GET_FPREG(var, d0) -#define SET_FP0(var) SET_FPREG(var, d0) -#endif - -int parent, child; - -void sigusr1(int s, siginfo_t* siginfo, void* _uc) { - // Fork and clobber %xmm0. The fpstate should be restored by sigreturn(2) - // in both parent and child. - child = fork(); - TEST_CHECK_MSG(child >= 0, "fork failed"); - - uint64_t val = SIGUSR1; - SET_FP0(val); - uint64_t got; - GET_FP0(got); - TEST_CHECK_MSG(val == got, "Basic FP check failed in sigusr1()"); -} - -TEST(FPSigTest, Fork) { - parent = getpid(); - pid_t parent_tid = gettid(); - - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = sigusr1; - ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); - - // The amd64 ABI specifies that the XMM register set is caller-saved. This - // implies that if there is any function call between SET_XMM and GET_XMM the - // compiler might save/restore xmm0 implicitly. This defeats the entire - // purpose of the test which is to verify that fpstate is restored by - // sigreturn(2). - // - // This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented - // in inline assembly below. - // - // If the OS is broken and registers are clobbered by the child, using tgkill - // to signal the current thread increases the likelihood that this thread will - // be the one clobbered. - - uint64_t expected = 0xdeadbeeffacefeed; - SET_FP0(expected); - -#ifdef __x86_64__ - asm volatile( - "movl %[killnr], %%eax;" - "movl %[parent], %%edi;" - "movl %[tid], %%esi;" - "movl %[sig], %%edx;" - "syscall;" - : - : [ killnr ] "i"(__NR_tgkill), [ parent ] "rm"(parent), - [ tid ] "rm"(parent_tid), [ sig ] "i"(SIGUSR1) - : "rax", "rdi", "rsi", "rdx", - // Clobbered by syscall. - "rcx", "r11"); -#elif __aarch64__ - asm volatile( - "mov x8, %0\n" - "mov x0, %1\n" - "mov x1, %2\n" - "mov x2, %3\n" - "svc #0\n" ::"r"(__NR_tgkill), - "r"(parent), "r"(parent_tid), "r"(SIGUSR1)); -#endif - - uint64_t got; - GET_FP0(got); - - if (getpid() == parent) { // Parent. - int status; - ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - } - - // TEST_CHECK_MSG since this may run in the child. - TEST_CHECK_MSG(expected == got, "Bad xmm0 value"); - - if (getpid() != parent) { // Child. - _exit(0); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fpsig_nested.cc b/test/syscalls/linux/fpsig_nested.cc deleted file mode 100644 index 302d928d1..000000000 --- a/test/syscalls/linux/fpsig_nested.cc +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This program verifies that application floating point state is restored -// correctly after a signal handler returns. It also verifies that this works -// with nested signals. -#include <sys/time.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#ifdef __x86_64__ -#define GET_XMM(__var, __xmm) \ - asm volatile("movq %%" #__xmm ", %0" : "=r"(__var)) -#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var)) -#define GET_FP0(__var) GET_XMM(__var, xmm0) -#define SET_FP0(__var) SET_XMM(__var, xmm0) -#elif __aarch64__ -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) -#define GET_FPREG(var, regname) \ - asm volatile("str " __stringify(regname) ", %0" : "=m"(var)) -#define SET_FPREG(var, regname) \ - asm volatile("ldr " __stringify(regname) ", %0" : "=m"(var)) -#define GET_FP0(var) GET_FPREG(var, d0) -#define SET_FP0(var) SET_FPREG(var, d0) -#endif - -int pid; -int tid; - -volatile uint64_t entryxmm[2] = {~0UL, ~0UL}; -volatile uint64_t exitxmm[2]; - -void sigusr2(int s, siginfo_t* siginfo, void* _uc) { - uint64_t val = SIGUSR2; - - // Record the value of %xmm0 on entry and then clobber it. - GET_FP0(entryxmm[1]); - SET_FP0(val); - GET_FP0(exitxmm[1]); -} - -void sigusr1(int s, siginfo_t* siginfo, void* _uc) { - uint64_t val = SIGUSR1; - - // Record the value of %xmm0 on entry and then clobber it. - GET_FP0(entryxmm[0]); - SET_FP0(val); - - // Send a SIGUSR2 to ourself. The signal mask is configured such that - // the SIGUSR2 handler will run before this handler returns. -#ifdef __x86_64__ - asm volatile( - "movl %[killnr], %%eax;" - "movl %[pid], %%edi;" - "movl %[tid], %%esi;" - "movl %[sig], %%edx;" - "syscall;" - : - : [ killnr ] "i"(__NR_tgkill), [ pid ] "rm"(pid), [ tid ] "rm"(tid), - [ sig ] "i"(SIGUSR2) - : "rax", "rdi", "rsi", "rdx", - // Clobbered by syscall. - "rcx", "r11"); -#elif __aarch64__ - asm volatile( - "mov x8, %0\n" - "mov x0, %1\n" - "mov x1, %2\n" - "mov x2, %3\n" - "svc #0\n" ::"r"(__NR_tgkill), - "r"(pid), "r"(tid), "r"(SIGUSR2)); -#endif - - // Record value of %xmm0 again to verify that the nested signal handler - // does not clobber it. - GET_FP0(exitxmm[0]); -} - -TEST(FPSigTest, NestedSignals) { - pid = getpid(); - tid = gettid(); - - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = sigusr1; - ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); - - sa.sa_sigaction = sigusr2; - ASSERT_THAT(sigaction(SIGUSR2, &sa, nullptr), SyscallSucceeds()); - - // The amd64 ABI specifies that the XMM register set is caller-saved. This - // implies that if there is any function call between SET_XMM and GET_XMM the - // compiler might save/restore xmm0 implicitly. This defeats the entire - // purpose of the test which is to verify that fpstate is restored by - // sigreturn(2). - // - // This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented - // in inline assembly below. - // - // If the OS is broken and registers are clobbered by the signal, using tgkill - // to signal the current thread ensures that this is the clobbered thread. - - uint64_t expected = 0xdeadbeeffacefeed; - SET_FP0(expected); - -#ifdef __x86_64__ - asm volatile( - "movl %[killnr], %%eax;" - "movl %[pid], %%edi;" - "movl %[tid], %%esi;" - "movl %[sig], %%edx;" - "syscall;" - : - : [ killnr ] "i"(__NR_tgkill), [ pid ] "rm"(pid), [ tid ] "rm"(tid), - [ sig ] "i"(SIGUSR1) - : "rax", "rdi", "rsi", "rdx", - // Clobbered by syscall. - "rcx", "r11"); -#elif __aarch64__ - asm volatile( - "mov x8, %0\n" - "mov x0, %1\n" - "mov x1, %2\n" - "mov x2, %3\n" - "svc #0\n" ::"r"(__NR_tgkill), - "r"(pid), "r"(tid), "r"(SIGUSR1)); -#endif - - uint64_t got; - GET_FP0(got); - - // - // The checks below verifies the following: - // - signal handlers must called with a clean fpu state. - // - sigreturn(2) must restore fpstate of the interrupted context. - // - EXPECT_EQ(expected, got); - EXPECT_EQ(entryxmm[0], 0); - EXPECT_EQ(entryxmm[1], 0); - EXPECT_EQ(exitxmm[0], SIGUSR1); - EXPECT_EQ(exitxmm[1], SIGUSR2); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/fsync.cc b/test/syscalls/linux/fsync.cc deleted file mode 100644 index e7e057f06..000000000 --- a/test/syscalls/linux/fsync.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <stdio.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(FsyncTest, TempFileSucceeds) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - const std::string data = "some data to sync"; - EXPECT_THAT(write(fd.get(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - EXPECT_THAT(fsync(fd.get()), SyscallSucceeds()); -} - -TEST(FsyncTest, TempDirSucceeds) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY)); - EXPECT_THAT(fsync(fd.get()), SyscallSucceeds()); -} - -TEST(FsyncTest, CannotFsyncOnUnopenedFd) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - int fd; - ASSERT_THAT(fd = open(file.path().c_str(), O_RDONLY), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - - // fd is now invalid. - EXPECT_THAT(fsync(fd), SyscallFailsWithErrno(EBADF)); -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/futex.cc b/test/syscalls/linux/futex.cc deleted file mode 100644 index 90b1f0508..000000000 --- a/test/syscalls/linux/futex.cc +++ /dev/null @@ -1,834 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <linux/futex.h> -#include <linux/types.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <memory> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/memory_util.h" -#include "test/util/save_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/time_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Amount of time we wait for threads doing futex_wait to start running before -// doing futex_wake. -constexpr auto kWaiterStartupDelay = absl::Seconds(3); - -// Default timeout for waiters in tests where we expect a futex_wake to be -// ineffective. -constexpr auto kIneffectiveWakeTimeout = absl::Seconds(6); - -static_assert(kWaiterStartupDelay < kIneffectiveWakeTimeout, - "futex_wait will time out before futex_wake is called"); - -int futex_wait(bool priv, std::atomic<int>* uaddr, int val, - absl::Duration timeout = absl::InfiniteDuration()) { - int op = FUTEX_WAIT; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - - if (timeout == absl::InfiniteDuration()) { - return RetryEINTR(syscall)(SYS_futex, uaddr, op, val, nullptr); - } - - // FUTEX_WAIT doesn't adjust the timeout if it returns EINTR, so we have to do - // so. - while (true) { - auto const timeout_ts = absl::ToTimespec(timeout); - MonotonicTimer timer; - timer.Start(); - int const ret = syscall(SYS_futex, uaddr, op, val, &timeout_ts); - if (ret != -1 || errno != EINTR) { - return ret; - } - timeout = std::max(timeout - timer.Duration(), absl::ZeroDuration()); - } -} - -int futex_wait_bitset(bool priv, std::atomic<int>* uaddr, int val, int bitset, - absl::Time deadline = absl::InfiniteFuture()) { - int op = FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - - auto const deadline_ts = absl::ToTimespec(deadline); - return RetryEINTR(syscall)( - SYS_futex, uaddr, op, val, - deadline == absl::InfiniteFuture() ? nullptr : &deadline_ts, nullptr, - bitset); -} - -int futex_wake(bool priv, std::atomic<int>* uaddr, int count) { - int op = FUTEX_WAKE; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr, op, count); -} - -int futex_wake_bitset(bool priv, std::atomic<int>* uaddr, int count, - int bitset) { - int op = FUTEX_WAKE_BITSET; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr, op, count, nullptr, nullptr, bitset); -} - -int futex_wake_op(bool priv, std::atomic<int>* uaddr1, std::atomic<int>* uaddr2, - int nwake1, int nwake2, uint32_t sub_op) { - int op = FUTEX_WAKE_OP; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - return syscall(SYS_futex, uaddr1, op, nwake1, nwake2, uaddr2, sub_op); -} - -int futex_lock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_LOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int zero = 0; - if (uaddr->compare_exchange_strong(zero, gettid())) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -int futex_trylock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_TRYLOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int zero = 0; - if (uaddr->compare_exchange_strong(zero, gettid())) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -int futex_unlock_pi(bool priv, std::atomic<int>* uaddr) { - int op = FUTEX_UNLOCK_PI; - if (priv) { - op |= FUTEX_PRIVATE_FLAG; - } - int tid = gettid(); - if (uaddr->compare_exchange_strong(tid, 0)) { - return 0; - } - return RetryEINTR(syscall)(SYS_futex, uaddr, op, nullptr, nullptr); -} - -// Fixture for futex tests parameterized by whether to use private or shared -// futexes. -class PrivateAndSharedFutexTest : public ::testing::TestWithParam<bool> { - protected: - bool IsPrivate() const { return GetParam(); } - int PrivateFlag() const { return IsPrivate() ? FUTEX_PRIVATE_FLAG : 0; } -}; - -// FUTEX_WAIT with 0 timeout does not block. -TEST_P(PrivateAndSharedFutexTest, Wait_ZeroTimeout) { - struct timespec timeout = {}; - - // Don't use the futex_wait helper because it adjusts timeout. - int a = 1; - EXPECT_THAT(syscall(SYS_futex, &a, FUTEX_WAIT | PrivateFlag(), a, &timeout), - SyscallFailsWithErrno(ETIMEDOUT)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_Timeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - constexpr absl::Duration kTimeout = absl::Seconds(1); - EXPECT_THAT(futex_wait(IsPrivate(), &a, a, kTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - EXPECT_GE(timer.Duration(), kTimeout); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_BitsetTimeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - constexpr absl::Duration kTimeout = absl::Seconds(1); - EXPECT_THAT( - futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff, absl::Now() + kTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - EXPECT_GE(timer.Duration(), kTimeout); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_NegativeTimeout) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - MonotonicTimer timer; - timer.Start(); - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0xffffffff, - absl::Now() - absl::Seconds(1)), - SyscallFailsWithErrno(ETIMEDOUT)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_WrongVal) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - EXPECT_THAT(futex_wait(IsPrivate(), &a, a + 1), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_ZeroBitset) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, a, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(PrivateAndSharedFutexTest, Wake1_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - // Prevent save/restore from interrupting futex_wait, which will cause it to - // return EAGAIN instead of the expected result if futex_wait is restarted - // after we change the value of a below. - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceedsWithValue(0)); - }); - absl::SleepFor(kWaiterStartupDelay); - - // Change a so that if futex_wake happens before futex_wait, the latter - // returns EAGAIN instead of hanging the test. - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, Wake0_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - // Prevent save/restore from interrupting futex_wait, which will cause it to - // return EAGAIN instead of the expected result if futex_wait is restarted - // after we change the value of a below. - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceedsWithValue(0)); - }); - absl::SleepFor(kWaiterStartupDelay); - - // Change a so that if futex_wake happens before futex_wait, the latter - // returns EAGAIN instead of hanging the test. - a.fetch_add(1); - // The Linux kernel wakes one waiter even if val is 0 or negative. - EXPECT_THAT(futex_wake(IsPrivate(), &a, 0), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeAll_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - constexpr int kThreads = 5; - std::vector<std::unique_ptr<ScopedThread>> threads; - threads.reserve(kThreads); - for (int i = 0; i < kThreads; i++) { - threads.push_back(absl::make_unique<ScopedThread>([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), - SyscallSucceeds()); - })); - } - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, kThreads), - SyscallSucceedsWithValue(kThreads)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeSome_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - constexpr int kThreads = 5; - constexpr int kWokenThreads = 3; - static_assert(kWokenThreads < kThreads, - "can't wake more threads than are created"); - std::vector<std::unique_ptr<ScopedThread>> threads; - threads.reserve(kThreads); - std::vector<int> rets; - rets.reserve(kThreads); - std::vector<int> errs; - errs.reserve(kThreads); - for (int i = 0; i < kThreads; i++) { - rets.push_back(-1); - errs.push_back(0); - } - for (int i = 0; i < kThreads; i++) { - threads.push_back(absl::make_unique<ScopedThread>([&, i] { - rets[i] = - futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout); - errs[i] = errno; - })); - } - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, kWokenThreads), - SyscallSucceedsWithValue(kWokenThreads)); - - int woken = 0; - int timedout = 0; - for (int i = 0; i < kThreads; i++) { - threads[i]->Join(); - if (rets[i] == 0) { - woken++; - } else if (errs[i] == ETIMEDOUT) { - timedout++; - } else { - ADD_FAILURE() << " thread " << i << ": returned " << rets[i] << ", errno " - << errs[i]; - } - } - EXPECT_EQ(woken, kWokenThreads); - EXPECT_EQ(timedout, kThreads - kWokenThreads); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_Wake_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, 0b01001000), - SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), &a, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, Wait_WakeBitset_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, 0b01001000), - SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetMatch_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - constexpr int kBitset = 0b01001000; - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kBitset), - SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kBitset), - SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WaitBitset_WakeBitsetNoMatch_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - constexpr int kWaitBitset = 0b01000001; - constexpr int kWakeBitset = 0b00101000; - static_assert((kWaitBitset & kWakeBitset) == 0, - "futex_wake_bitset will wake waiter"); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait_bitset(IsPrivate(), &a, kInitialValue, kWaitBitset, - absl::Now() + kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - EXPECT_THAT(futex_wake_bitset(IsPrivate(), &a, 1, kWakeBitset), - SyscallSucceedsWithValue(0)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeOpCondSuccess_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread_a([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - ScopedThread thread_b([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &b, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - b.fetch_add(1); - // This futex_wake_op should: - // - Wake 1 waiter on a unconditionally. - // - Wake 1 waiter on b if b == kInitialValue + 1, which it is. - // - Do "b += 1". - EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1, - FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ, - (kInitialValue + 1))), - SyscallSucceedsWithValue(2)); - EXPECT_EQ(b, kInitialValue + 2); -} - -TEST_P(PrivateAndSharedFutexTest, WakeOpCondFailure_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - std::atomic<int> b = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread_a([&] { - EXPECT_THAT(futex_wait(IsPrivate(), &a, kInitialValue), SyscallSucceeds()); - }); - ScopedThread thread_b([&] { - EXPECT_THAT( - futex_wait(IsPrivate(), &b, kInitialValue, kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - b.fetch_add(1); - // This futex_wake_op should: - // - Wake 1 waiter on a unconditionally. - // - Wake 1 waiter on b if b == kInitialValue - 1, which it isn't. - // - Do "b += 1". - EXPECT_THAT(futex_wake_op(IsPrivate(), &a, &b, 1, 1, - FUTEX_OP(FUTEX_OP_ADD, 1, FUTEX_OP_CMP_EQ, - (kInitialValue - 1))), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(b, kInitialValue + 2); -} - -TEST_P(PrivateAndSharedFutexTest, NoWakeInterprocessPrivateAnon_NoRandomSave) { - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(IsPrivate(), ptr, kInitialValue, - kIneffectiveWakeTimeout) == -1 && - errno == ETIMEDOUT); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - absl::SleepFor(kWaiterStartupDelay); - - EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(0)); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST_P(PrivateAndSharedFutexTest, WakeAfterCOWBreak_NoRandomSave) { - // Use a futex on a non-stack mapping so we can be sure that the child process - // below isn't the one that breaks copy-on-write. - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT(futex_wait(IsPrivate(), ptr, kInitialValue), SyscallSucceeds()); - }); - absl::SleepFor(kWaiterStartupDelay); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Wait to be killed by the parent. - while (true) pause(); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto cleanup_child = Cleanup([&] { - EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; - }); - - // In addition to preventing a late futex_wait from sleeping, this breaks - // copy-on-write on the mapped page. - ptr->fetch_add(1); - EXPECT_THAT(futex_wake(IsPrivate(), ptr, 1), SyscallSucceedsWithValue(1)); -} - -TEST_P(PrivateAndSharedFutexTest, WakeWrongKind_NoRandomSave) { - constexpr int kInitialValue = 1; - std::atomic<int> a = ATOMIC_VAR_INIT(kInitialValue); - - DisableSave ds; - ScopedThread thread([&] { - EXPECT_THAT( - futex_wait(IsPrivate(), &a, kInitialValue, kIneffectiveWakeTimeout), - SyscallFailsWithErrno(ETIMEDOUT)); - }); - absl::SleepFor(kWaiterStartupDelay); - - a.fetch_add(1); - // The value of priv passed to futex_wake is the opposite of that passed to - // the futex_waiter; we expect this not to wake the waiter. - EXPECT_THAT(futex_wake(!IsPrivate(), &a, 1), SyscallSucceedsWithValue(0)); -} - -INSTANTIATE_TEST_SUITE_P(SharedPrivate, PrivateAndSharedFutexTest, - ::testing::Bool()); - -// Passing null as the address only works for private futexes. - -TEST(PrivateFutexTest, WakeOp0Set) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - - int futex_op = FUTEX_OP(FUTEX_OP_SET, 2, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 2); -} - -TEST(PrivateFutexTest, WakeOp0Add) { - std::atomic<int> a = ATOMIC_VAR_INIT(1); - int futex_op = FUTEX_OP(FUTEX_OP_ADD, 1, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 2); -} - -TEST(PrivateFutexTest, WakeOp0Or) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b01); - int futex_op = FUTEX_OP(FUTEX_OP_OR, 0b10, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b11); -} - -TEST(PrivateFutexTest, WakeOp0Andn) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b11); - int futex_op = FUTEX_OP(FUTEX_OP_ANDN, 0b10, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b01); -} - -TEST(PrivateFutexTest, WakeOp0Xor) { - std::atomic<int> a = ATOMIC_VAR_INIT(0b1010); - int futex_op = FUTEX_OP(FUTEX_OP_XOR, 0b1100, 0, 0); - EXPECT_THAT(futex_wake_op(true, nullptr, &a, 0, 0, futex_op), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(a, 0b0110); -} - -TEST(SharedFutexTest, WakeInterprocessSharedAnon_NoRandomSave) { - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto kill_child = Cleanup( - [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); }); - absl::SleepFor(kWaiterStartupDelay); - - ptr->fetch_add(1); - // This is an ASSERT so that if it fails, we immediately abort the test (and - // kill the subprocess). - ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1)); - - kill_child.Release(); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(SharedFutexTest, WakeInterprocessFile_NoRandomSave) { - auto const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_THAT(truncate(file.path().c_str(), kPageSize), SyscallSucceeds()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - auto const ptr = static_cast<std::atomic<int>*>(mapping.ptr()); - constexpr int kInitialValue = 1; - ptr->store(kInitialValue); - - DisableSave ds; - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(futex_wait(false, ptr, kInitialValue) == 0); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - auto kill_child = Cleanup( - [&] { EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); }); - absl::SleepFor(kWaiterStartupDelay); - - ptr->fetch_add(1); - // This is an ASSERT so that if it fails, we immediately abort the test (and - // kill the subprocess). - ASSERT_THAT(futex_wake(false, ptr, 1), SyscallSucceedsWithValue(1)); - - kill_child.Release(); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST_P(PrivateAndSharedFutexTest, PIBasic) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - - ASSERT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - EXPECT_THAT(futex_lock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EDEADLK)); - - ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), 0); - EXPECT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallFailsWithErrno(EPERM)); -} - -TEST_P(PrivateAndSharedFutexTest, PIConcurrency_NoRandomSave) { - DisableSave ds; // Too many syscalls. - - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - std::unique_ptr<ScopedThread> threads[100]; - for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { - threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] { - for (size_t j = 0; j < 10; ++j) { - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid()); - SleepSafe(absl::Milliseconds(5)); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - } - }); - } -} - -TEST_P(PrivateAndSharedFutexTest, PIWaiters) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - - ScopedThread th([is_priv, &a] { - ASSERT_THAT(futex_lock_pi(is_priv, &a), SyscallSucceeds()); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - }); - - // Wait until the thread blocks on the futex, setting the waiters bit. - auto start = absl::Now(); - while (a.load() != (FUTEX_WAITERS | gettid())) { - ASSERT_LT(absl::Now() - start, absl::Seconds(5)); - absl::SleepFor(absl::Milliseconds(100)); - } - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); -} - -TEST_P(PrivateAndSharedFutexTest, PITryLock) { - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - ASSERT_THAT(futex_trylock_pi(IsPrivate(), &a), SyscallSucceeds()); - EXPECT_EQ(a.load(), gettid()); - - EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EDEADLK)); - ScopedThread th([is_priv, &a] { - EXPECT_THAT(futex_trylock_pi(is_priv, &a), SyscallFailsWithErrno(EAGAIN)); - }); - th.Join(); - - ASSERT_THAT(futex_unlock_pi(IsPrivate(), &a), SyscallSucceeds()); -} - -TEST_P(PrivateAndSharedFutexTest, PITryLockConcurrency_NoRandomSave) { - DisableSave ds; // Too many syscalls. - - std::atomic<int> a = ATOMIC_VAR_INIT(0); - const bool is_priv = IsPrivate(); - - std::unique_ptr<ScopedThread> threads[10]; - for (size_t i = 0; i < ABSL_ARRAYSIZE(threads); ++i) { - threads[i] = absl::make_unique<ScopedThread>([is_priv, &a] { - for (size_t j = 0; j < 10;) { - if (futex_trylock_pi(is_priv, &a) == 0) { - ++j; - EXPECT_EQ(a.load() & FUTEX_TID_MASK, gettid()); - SleepSafe(absl::Milliseconds(5)); - ASSERT_THAT(futex_unlock_pi(is_priv, &a), SyscallSucceeds()); - } - } - }); - } -} - -int get_robust_list(int pid, struct robust_list_head** head_ptr, - size_t* len_ptr) { - return syscall(__NR_get_robust_list, pid, head_ptr, len_ptr); -} - -int set_robust_list(struct robust_list_head* head, size_t len) { - return syscall(__NR_set_robust_list, head, len); -} - -TEST(RobustFutexTest, BasicSetGet) { - struct robust_list_head hd = {}; - struct robust_list_head* hd_ptr = &hd; - - // Set! - EXPECT_THAT(set_robust_list(hd_ptr, sizeof(hd)), SyscallSucceedsWithValue(0)); - - // Get! - struct robust_list_head* new_hd_ptr = hd_ptr; - size_t len; - EXPECT_THAT(get_robust_list(0, &new_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(new_hd_ptr, hd_ptr); - EXPECT_EQ(len, sizeof(hd)); -} - -TEST(RobustFutexTest, GetFromOtherTid) { - // Get the current tid and list head. - pid_t tid = gettid(); - struct robust_list_head* hd_ptr = {}; - size_t len; - EXPECT_THAT(get_robust_list(0, &hd_ptr, &len), SyscallSucceedsWithValue(0)); - - // Create a new thread. - ScopedThread t([&] { - // Current tid list head should be different from parent tid. - struct robust_list_head* got_hd_ptr = {}; - EXPECT_THAT(get_robust_list(0, &got_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_NE(hd_ptr, got_hd_ptr); - - // Get the parent list head by passing its tid. - EXPECT_THAT(get_robust_list(tid, &got_hd_ptr, &len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(hd_ptr, got_hd_ptr); - }); - - // Wait for thread. - t.Join(); -} - -TEST(RobustFutexTest, InvalidSize) { - struct robust_list_head* hd = {}; - EXPECT_THAT(set_robust_list(hd, sizeof(*hd) + 1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(RobustFutexTest, PthreadMutexAttr) { - constexpr int kNumMutexes = 3; - - // Create a bunch of robust mutexes. - pthread_mutexattr_t attrs[kNumMutexes]; - pthread_mutex_t mtxs[kNumMutexes]; - for (int i = 0; i < kNumMutexes; i++) { - TEST_PCHECK(pthread_mutexattr_init(&attrs[i]) == 0); - TEST_PCHECK(pthread_mutexattr_setrobust(&attrs[i], PTHREAD_MUTEX_ROBUST) == - 0); - TEST_PCHECK(pthread_mutex_init(&mtxs[i], &attrs[i]) == 0); - } - - // Start thread to lock the mutexes and then exit. - ScopedThread t([&] { - for (int i = 0; i < kNumMutexes; i++) { - TEST_PCHECK(pthread_mutex_lock(&mtxs[i]) == 0); - } - pthread_exit(NULL); - }); - - // Wait for thread. - t.Join(); - - // Now try to take the mutexes. - for (int i = 0; i < kNumMutexes; i++) { - // Should get EOWNERDEAD. - EXPECT_EQ(pthread_mutex_lock(&mtxs[i]), EOWNERDEAD); - // Make the mutex consistent. - EXPECT_EQ(pthread_mutex_consistent(&mtxs[i]), 0); - // Unlock. - EXPECT_EQ(pthread_mutex_unlock(&mtxs[i]), 0); - } -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/getcpu.cc b/test/syscalls/linux/getcpu.cc deleted file mode 100644 index f4d94bd6a..000000000 --- a/test/syscalls/linux/getcpu.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sched.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(GetcpuTest, IsValidCpuStress) { - const int num_cpus = NumCPUs(); - absl::Time deadline = absl::Now() + absl::Seconds(10); - while (absl::Now() < deadline) { - int cpu; - ASSERT_THAT(cpu = sched_getcpu(), SyscallSucceeds()); - ASSERT_LT(cpu, num_cpus); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc deleted file mode 100644 index 2f2b14037..000000000 --- a/test/syscalls/linux/getdents.cc +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <stddef.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/types.h> -#include <syscall.h> -#include <unistd.h> - -#include <map> -#include <string> -#include <unordered_map> -#include <unordered_set> -#include <utility> - -#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" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::Contains; -using ::testing::IsEmpty; -using ::testing::IsSupersetOf; -using ::testing::Not; -using ::testing::NotNull; - -namespace gvisor { -namespace testing { - -namespace { - -// New Linux dirent format. -struct linux_dirent64 { - uint64_t d_ino; // Inode number - int64_t d_off; // Offset to next linux_dirent64 - unsigned short d_reclen; // NOLINT, Length of this linux_dirent64 - unsigned char d_type; // NOLINT, File type - char d_name[0]; // Filename (null-terminated) -}; - -// Old Linux dirent format. -struct linux_dirent { - unsigned long d_ino; // NOLINT - unsigned long d_off; // NOLINT - unsigned short d_reclen; // NOLINT - char d_name[0]; -}; - -// Wraps a buffer to provide a set of dirents. -// T is the underlying dirent type. -template <typename T> -class DirentBuffer { - public: - // DirentBuffer manages the buffer. - explicit DirentBuffer(size_t size) - : managed_(true), actual_size_(size), reported_size_(size) { - data_ = new char[actual_size_]; - } - - // The buffer is managed externally. - DirentBuffer(char* data, size_t actual_size, size_t reported_size) - : managed_(false), - data_(data), - actual_size_(actual_size), - reported_size_(reported_size) {} - - ~DirentBuffer() { - if (managed_) { - delete[] data_; - } - } - - T* Data() { return reinterpret_cast<T*>(data_); } - - T* Start(size_t read) { - read_ = read; - if (read_) { - return Data(); - } else { - return nullptr; - } - } - - T* Current() { return reinterpret_cast<T*>(&data_[off_]); } - - T* Next() { - size_t new_off = off_ + Current()->d_reclen; - if (new_off >= read_ || new_off >= actual_size_) { - return nullptr; - } - - off_ = new_off; - return Current(); - } - - size_t Size() { return reported_size_; } - - void Reset() { - off_ = 0; - read_ = 0; - memset(data_, 0, actual_size_); - } - - private: - bool managed_; - char* data_; - size_t actual_size_; - size_t reported_size_; - - size_t off_ = 0; - - size_t read_ = 0; -}; - -// Test for getdents/getdents64. -// T is the Linux dirent type. -template <typename T> -class GetdentsTest : public ::testing::Test { - public: - using LinuxDirentType = T; - using DirentBufferType = DirentBuffer<T>; - - protected: - void SetUp() override { - dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(dir_.path(), O_RDONLY | O_DIRECTORY)); - } - - // Must be overridden with explicit specialization. See below. - int SyscallNum(); - - int Getdents(LinuxDirentType* dirp, unsigned int count) { - return RetryEINTR(syscall)(SyscallNum(), fd_.get(), dirp, count); - } - - // Fill directory with num files, named by number starting at 0. - void FillDirectory(size_t num) { - for (size_t i = 0; i < num; i++) { - auto name = JoinPath(dir_.path(), absl::StrCat(i)); - TEST_CHECK(CreateWithContents(name, "").ok()); - } - } - - // Fill directory with a given list of filenames. - void FillDirectoryWithFiles(const std::vector<std::string>& filenames) { - for (const auto& filename : filenames) { - auto name = JoinPath(dir_.path(), filename); - TEST_CHECK(CreateWithContents(name, "").ok()); - } - } - - // Seek to the start of the directory. - PosixError SeekStart() { - constexpr off_t kStartOfFile = 0; - off_t offset = lseek(fd_.get(), kStartOfFile, SEEK_SET); - if (offset < 0) { - return PosixError(errno, absl::StrCat("error seeking to ", kStartOfFile)); - } - if (offset != kStartOfFile) { - return PosixError(EINVAL, absl::StrCat("tried to seek to ", kStartOfFile, - " but got ", offset)); - } - return NoError(); - } - - // Call getdents multiple times, reading all dirents and calling f on each. - // f has the type signature PosixError f(T*). - // If f returns a non-OK error, so does ReadDirents. - template <typename F> - PosixError ReadDirents(DirentBufferType* dirents, F const& f) { - int n; - do { - dirents->Reset(); - - n = Getdents(dirents->Data(), dirents->Size()); - MaybeSave(); - if (n < 0) { - return PosixError(errno, "getdents"); - } - - for (auto d = dirents->Start(n); d; d = dirents->Next()) { - RETURN_IF_ERRNO(f(d)); - } - } while (n > 0); - - return NoError(); - } - - // Call Getdents successively and count all entries. - int ReadAndCountAllEntries(DirentBufferType* dirents) { - int found = 0; - - EXPECT_NO_ERRNO(ReadDirents(dirents, [&](LinuxDirentType* d) { - found++; - return NoError(); - })); - - return found; - } - - private: - TempPath dir_; - FileDescriptor fd_; -}; - -// Multiple template parameters are not allowed, so we must use explicit -// template specialization to set the syscall number. - -// SYS_getdents isn't defined on arm64. -#ifdef __x86_64__ -template <> -int GetdentsTest<struct linux_dirent>::SyscallNum() { - return SYS_getdents; -} -#endif - -template <> -int GetdentsTest<struct linux_dirent64>::SyscallNum() { - return SYS_getdents64; -} - -#ifdef __x86_64__ -// Test both legacy getdents and getdents64 on x86_64. -typedef ::testing::Types<struct linux_dirent, struct linux_dirent64> - GetdentsTypes; -#elif __aarch64__ -// Test only getdents64 on arm64. -typedef ::testing::Types<struct linux_dirent64> GetdentsTypes; -#endif -TYPED_TEST_SUITE(GetdentsTest, GetdentsTypes); - -// N.B. TYPED_TESTs require explicitly using this-> to access members of -// GetdentsTest, since we are inside of a derived class template. - -TYPED_TEST(GetdentsTest, VerifyEntries) { - typename TestFixture::DirentBufferType dirents(1024); - - this->FillDirectory(2); - - // Map of all the entries we expect to find. - std::map<std::string, bool> found; - found["."] = false; - found[".."] = false; - found["0"] = false; - found["1"] = false; - - EXPECT_NO_ERRNO(this->ReadDirents( - &dirents, [&](typename TestFixture::LinuxDirentType* d) { - auto kv = found.find(d->d_name); - EXPECT_NE(kv, found.end()) << "Unexpected file: " << d->d_name; - if (kv != found.end()) { - EXPECT_FALSE(kv->second); - } - found[d->d_name] = true; - return NoError(); - })); - - for (auto& kv : found) { - EXPECT_TRUE(kv.second) << "File not found: " << kv.first; - } -} - -TYPED_TEST(GetdentsTest, VerifyPadding) { - typename TestFixture::DirentBufferType dirents(1024); - - // Create files with names of length 1 through 16. - std::vector<std::string> files; - std::string filename; - for (int i = 0; i < 16; ++i) { - absl::StrAppend(&filename, "a"); - files.push_back(filename); - } - this->FillDirectoryWithFiles(files); - - // We expect to find all the files, plus '.' and '..'. - const int expect_found = 2 + files.size(); - int found = 0; - - EXPECT_NO_ERRNO(this->ReadDirents( - &dirents, [&](typename TestFixture::LinuxDirentType* d) { - EXPECT_EQ(d->d_reclen % 8, 0) - << "Dirent " << d->d_name - << " had reclen that was not byte aligned: " << d->d_name; - found++; - return NoError(); - })); - - // Make sure we found all the files. - EXPECT_EQ(found, expect_found); -} - -// For a small directory, the provided buffer should be large enough -// for all entries. -TYPED_TEST(GetdentsTest, SmallDir) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(2); - expect += 2; - - typename TestFixture::DirentBufferType dirents(256); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// A directory with lots of files requires calling getdents multiple times. -TYPED_TEST(GetdentsTest, LargeDir) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(100); - expect += 100; - - typename TestFixture::DirentBufferType dirents(256); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// If we lie about the size of the buffer, we should still be able to read the -// entries with the available space. -TYPED_TEST(GetdentsTest, PartialBuffer) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some actual files. - this->FillDirectory(100); - expect += 100; - - void* addr = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - ASSERT_NE(addr, MAP_FAILED); - - char* buf = reinterpret_cast<char*>(addr); - - // Guard page - EXPECT_THAT( - mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE), - SyscallSucceeds()); - - // Limit space in buf to 256 bytes. - buf += kPageSize - 256; - - // Lie about the buffer. Even though we claim the buffer is 1 page, - // we should still get all of the dirents in the first 256 bytes. - typename TestFixture::DirentBufferType dirents(buf, 256, kPageSize); - - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); - - EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()); -} - -// Open many file descriptors, then scan through /proc/self/fd to find and close -// them all. (The latter is commonly used to handle races between fork/execve -// and the creation of unwanted non-O_CLOEXEC file descriptors.) This tests that -// getdents iterates correctly despite mutation of /proc/self/fd. -TYPED_TEST(GetdentsTest, ProcSelfFd) { - constexpr size_t kNfds = 10; - 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()); - fds.emplace(fd.get(), std::move(fd)); - } - - const FileDescriptor proc_self_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/fd", O_RDONLY | O_DIRECTORY)); - - // Make the buffer very small since we want to iterate. - typename TestFixture::DirentBufferType dirents( - 2 * sizeof(typename TestFixture::LinuxDirentType)); - absl::node_hash_set<int> prev_fds; - while (true) { - dirents.Reset(); - int rv; - ASSERT_THAT(rv = RetryEINTR(syscall)(this->SyscallNum(), proc_self_fd.get(), - dirents.Data(), dirents.Size()), - SyscallSucceeds()); - if (rv == 0) { - break; - } - for (auto* d = dirents.Start(rv); d; d = dirents.Next()) { - int dfd; - if (!absl::SimpleAtoi(d->d_name, &dfd)) continue; - EXPECT_TRUE(prev_fds.insert(dfd).second) - << "Repeated observation of /proc/self/fd/" << dfd; - fds.erase(dfd); - } - } - - // Check that we closed every fd. - EXPECT_THAT(fds, ::testing::IsEmpty()); -} - -// Test that getdents returns ENOTDIR when called on a file. -TYPED_TEST(GetdentsTest, NotDir) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - typename TestFixture::DirentBufferType dirents(256); - EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallFailsWithErrno(ENOTDIR)); -} - -// Test that getdents returns EBADF when called on an opath file. -TYPED_TEST(GetdentsTest, OpathFile) { - SKIP_IF(IsRunningWithVFS1()); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - typename TestFixture::DirentBufferType dirents(256); - EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallFailsWithErrno(EBADF)); -} - -// Test that getdents returns EBADF when called on an opath directory. -TYPED_TEST(GetdentsTest, OpathDirectory) { - SKIP_IF(IsRunningWithVFS1()); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_PATH | O_DIRECTORY)); - - typename TestFixture::DirentBufferType dirents(256); - ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallFailsWithErrno(EBADF)); -} - -// Test that SEEK_SET to 0 causes getdents to re-read the entries. -TYPED_TEST(GetdentsTest, SeekResetsCursor) { - // . and .. should be in an otherwise empty directory. - int expect = 2; - - // Add some files to the directory. - this->FillDirectory(10); - expect += 10; - - typename TestFixture::DirentBufferType dirents(256); - - // We should get all the expected entries. - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); - - // Seek back to 0. - ASSERT_NO_ERRNO(this->SeekStart()); - - // We should get all the expected entries again. - EXPECT_EQ(expect, this->ReadAndCountAllEntries(&dirents)); -} - -// Test that getdents() after SEEK_END succeeds. -// This is a regression test for #128. -TYPED_TEST(GetdentsTest, Issue128ProcSeekEnd) { - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY | O_DIRECTORY)); - typename TestFixture::DirentBufferType dirents(256); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), - dirents.Size()), - SyscallSucceeds()); -} - -// Some tests using the glibc readdir interface. -TEST(ReaddirTest, OpenDir) { - DIR* dev; - ASSERT_THAT(dev = opendir("/dev"), NotNull()); - EXPECT_THAT(closedir(dev), SyscallSucceeds()); -} - -TEST(ReaddirTest, RootContainsBasicDirectories) { - EXPECT_THAT(ListDir("/", true), - IsPosixErrorOkAndHolds(IsSupersetOf( - {"bin", "dev", "etc", "lib", "proc", "sbin", "usr"}))); -} - -TEST(ReaddirTest, Bug24096713Dev) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug24096713ProcTid) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE( - ListDir(absl::StrCat("/proc/", syscall(SYS_gettid), "/"), true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug33429925Proc) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -TEST(ReaddirTest, Bug35110122Root) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/", true)); - EXPECT_THAT(contents, Not(IsEmpty())); -} - -// Unlink should invalidate getdents cache. -TEST(ReaddirTest, GoneAfterRemoveCache) { - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - std::string name = std::string(Basename(file.path())); - - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true)); - EXPECT_THAT(contents, Contains(name)); - - file.reset(); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), true)); - EXPECT_THAT(contents, Not(Contains(name))); -} - -// Regression test for b/137398511. Rename should invalidate getdents cache. -TEST(ReaddirTest, GoneAfterRenameCache) { - TempPath src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(src.path())); - std::string name = std::string(Basename(file.path())); - - auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true)); - EXPECT_THAT(contents, Contains(name)); - - ASSERT_THAT(rename(file.path().c_str(), JoinPath(dst.path(), name).c_str()), - SyscallSucceeds()); - // Release file since it was renamed. dst cleanup will ultimately delete it. - file.release(); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(src.path(), true)); - EXPECT_THAT(contents, Not(Contains(name))); - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dst.path(), true)); - EXPECT_THAT(contents, Contains(name)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/getrandom.cc b/test/syscalls/linux/getrandom.cc deleted file mode 100644 index f87cdd7a1..000000000 --- a/test/syscalls/linux/getrandom.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#ifndef SYS_getrandom -#if defined(__x86_64__) -#define SYS_getrandom 318 -#elif defined(__i386__) -#define SYS_getrandom 355 -#elif defined(__aarch64__) -#define SYS_getrandom 278 -#else -#error "Unknown architecture" -#endif -#endif // SYS_getrandom - -bool SomeByteIsNonZero(char* random_bytes, int length) { - for (int i = 0; i < length; i++) { - if (random_bytes[i] != 0) { - return true; - } - } - return false; -} - -TEST(GetrandomTest, IsRandom) { - // This test calls get_random and makes sure that the array is filled in with - // something that is non-zero. Perhaps we get back \x00\x00\x00\x00\x00.... as - // a random result, but it's so unlikely that we'll just ignore this. - char random_bytes[64] = {}; - int n = syscall(SYS_getrandom, random_bytes, 64, 0); - SKIP_IF(!IsRunningOnGvisor() && n < 0 && errno == ENOSYS); - EXPECT_THAT(n, SyscallSucceeds()); - EXPECT_GT(n, 0); // Some bytes should be returned. - EXPECT_TRUE(SomeByteIsNonZero(random_bytes, n)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/getrusage.cc b/test/syscalls/linux/getrusage.cc deleted file mode 100644 index e84cbfdc3..000000000 --- a/test/syscalls/linux/getrusage.cc +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/mman.h> -#include <sys/resource.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(GetrusageTest, BasicFork) { - pid_t pid = fork(); - if (pid == 0) { - struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); - struct rusage rusage_children; - TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - // The child has no children of its own. - TEST_CHECK(rusage_children.ru_maxrss == 0); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds()); - struct rusage rusage_self; - ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds()); - struct rusage rusage_children; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds()); - // The parent has consumed some memory. - EXPECT_GT(rusage_self.ru_maxrss, 0); - // The child has consumed some memory, and because it has exited we can get - // its max RSS. - EXPECT_GT(rusage_children.ru_maxrss, 0); -} - -// Verifies that a process can get the max resident set size of its grandchild, -// i.e. that maxrss propagates correctly from children to waiting parents. -TEST(GetrusageTest, Grandchild) { - constexpr int kGrandchildSizeKb = 1024; - pid_t pid = fork(); - if (pid == 0) { - pid = fork(); - if (pid == 0) { - int flags = MAP_ANONYMOUS | MAP_POPULATE | MAP_PRIVATE; - void* addr = - mmap(nullptr, kGrandchildSizeKb * 1024, PROT_WRITE, flags, -1, 0); - TEST_PCHECK(addr != MAP_FAILED); - } else { - int status; - TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0) == pid); - } - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds()); - struct rusage rusage_self; - ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds()); - struct rusage rusage_children; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds()); - // The parent has consumed some memory. - EXPECT_GT(rusage_self.ru_maxrss, 0); - // The child should consume next to no memory, but the grandchild will - // consume at least 1MB. Verify that usage bubbles up to the grandparent. - EXPECT_GT(rusage_children.ru_maxrss, kGrandchildSizeKb); -} - -// Verifies that processes ignoring SIGCHLD do not have updated child maxrss -// updated. -TEST(GetrusageTest, IgnoreSIGCHLD) { - const auto rest = [] { - struct sigaction sa; - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - auto cleanup = TEST_CHECK_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa)); - pid_t pid = fork(); - if (pid == 0) { - struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - _exit(0); - } - TEST_CHECK_SUCCESS(pid); - int status; - TEST_CHECK_ERRNO(RetryEINTR(waitpid)(pid, &status, 0), ECHILD); - struct rusage rusage_self; - TEST_CHECK_SUCCESS(getrusage(RUSAGE_SELF, &rusage_self)); - struct rusage rusage_children; - TEST_CHECK_SUCCESS(getrusage(RUSAGE_CHILDREN, &rusage_children)); - // The parent has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss > 0); - // The child's maxrss should not have propagated up. - TEST_CHECK(rusage_children.ru_maxrss == 0); - }; - // Execute inside a forked process so that rusage_children is clean. - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// Verifies that zombie processes do not update their parent's maxrss. Only -// reaped processes should do this. -TEST(GetrusageTest, IgnoreZombie) { - const auto rest = [] { - pid_t pid = fork(); - if (pid == 0) { - struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); - struct rusage rusage_children; - TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - // The child has no children of its own. - TEST_CHECK(rusage_children.ru_maxrss == 0); - _exit(0); - } - TEST_CHECK_SUCCESS(pid); - // Give the child time to exit. Because we don't call wait, the child should - // remain a zombie. - absl::SleepFor(absl::Seconds(5)); - struct rusage rusage_self; - TEST_CHECK_SUCCESS(getrusage(RUSAGE_SELF, &rusage_self)); - struct rusage rusage_children; - TEST_CHECK_SUCCESS(getrusage(RUSAGE_CHILDREN, &rusage_children)); - // The parent has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss > 0); - // The child has consumed some memory, but hasn't been reaped. - TEST_CHECK(rusage_children.ru_maxrss == 0); - }; - // Execute inside a forked process so that rusage_children is clean. - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST(GetrusageTest, Wait4) { - pid_t pid = fork(); - if (pid == 0) { - struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); - struct rusage rusage_children; - TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - // The child has no children of its own. - TEST_CHECK(rusage_children.ru_maxrss == 0); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - struct rusage rusage_children; - int status; - ASSERT_THAT(RetryEINTR(wait4)(pid, &status, 0, &rusage_children), - SyscallSucceeds()); - // The child has consumed some memory, and because it has exited we can get - // its max RSS. - EXPECT_GT(rusage_children.ru_maxrss, 0); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc deleted file mode 100644 index a88c89e20..000000000 --- a/test/syscalls/linux/inotify.cc +++ /dev/null @@ -1,2532 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <libgen.h> -#include <sched.h> -#include <sys/epoll.h> -#include <sys/inotify.h> -#include <sys/ioctl.h> -#include <sys/sendfile.h> -#include <sys/time.h> -#include <sys/xattr.h> - -#include <atomic> -#include <list> -#include <string> -#include <vector> - -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/epoll_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using ::absl::StreamFormat; -using ::absl::StrFormat; - -constexpr int kBufSize = 1024; - -// C++-friendly version of struct inotify_event. -struct Event { - int32_t wd; - uint32_t mask; - uint32_t cookie; - uint32_t len; - std::string name; - - Event(uint32_t mask, int32_t wd, absl::string_view name, uint32_t cookie) - : wd(wd), - mask(mask), - cookie(cookie), - len(name.size()), - name(std::string(name)) {} - Event(uint32_t mask, int32_t wd, absl::string_view name) - : Event(mask, wd, name, 0) {} - Event(uint32_t mask, int32_t wd) : Event(mask, wd, "", 0) {} - Event() : Event(0, 0, "", 0) {} -}; - -// Prints the symbolic name for a struct inotify_event's 'mask' field. -std::string FlagString(uint32_t flags) { - std::vector<std::string> names; - -#define EMIT(target) \ - if (flags & target) { \ - names.push_back(#target); \ - flags &= ~target; \ - } - - EMIT(IN_ACCESS); - EMIT(IN_ATTRIB); - EMIT(IN_CLOSE_WRITE); - EMIT(IN_CLOSE_NOWRITE); - EMIT(IN_CREATE); - EMIT(IN_DELETE); - EMIT(IN_DELETE_SELF); - EMIT(IN_MODIFY); - EMIT(IN_MOVE_SELF); - EMIT(IN_MOVED_FROM); - EMIT(IN_MOVED_TO); - EMIT(IN_OPEN); - - EMIT(IN_DONT_FOLLOW); - EMIT(IN_EXCL_UNLINK); - EMIT(IN_ONESHOT); - EMIT(IN_ONLYDIR); - - EMIT(IN_IGNORED); - EMIT(IN_ISDIR); - EMIT(IN_Q_OVERFLOW); - EMIT(IN_UNMOUNT); - -#undef EMIT - - // If we have anything left over at the end, print it as a hex value. - if (flags) { - names.push_back(absl::StrCat("0x", absl::Hex(flags))); - } - - return absl::StrJoin(names, "|"); -} - -std::string DumpEvent(const Event& event) { - return StrFormat( - "%s, wd=%d%s%s", FlagString(event.mask), event.wd, - (event.len > 0) ? StrFormat(", name=%s", event.name) : "", - (event.cookie > 0) ? StrFormat(", cookie=%ud", event.cookie) : ""); -} - -std::string DumpEvents(const std::vector<Event>& events, int indent_level) { - std::stringstream ss; - ss << StreamFormat("%d event%s:\n", events.size(), - (events.size() > 1) ? "s" : ""); - int i = 0; - for (const Event& ev : events) { - ss << StreamFormat("%sevents[%d]: %s\n", std::string(indent_level, '\t'), - i++, DumpEvent(ev)); - } - return ss.str(); -} - -// A matcher which takes an expected list of events to match against another -// list of inotify events, in order. This is similar to the ElementsAre matcher, -// but displays more informative messages on mismatch. -class EventsAreMatcher - : public ::testing::MatcherInterface<std::vector<Event>> { - public: - explicit EventsAreMatcher(std::vector<Event> references) - : references_(std::move(references)) {} - - bool MatchAndExplain( - std::vector<Event> events, - ::testing::MatchResultListener* const listener) const override { - if (references_.size() != events.size()) { - *listener << StreamFormat("\n\tCount mismatch, got %s", - DumpEvents(events, 2)); - return false; - } - - bool success = true; - for (unsigned int i = 0; i < references_.size(); ++i) { - const Event& reference = references_[i]; - const Event& target = events[i]; - - if (target.mask != reference.mask || target.wd != reference.wd || - target.name != reference.name || target.cookie != reference.cookie) { - *listener << StreamFormat("\n\tMismatch at index %d, want %s, got %s,", - i, DumpEvent(reference), DumpEvent(target)); - success = false; - } - } - - if (!success) { - *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2)); - } - return success; - } - - void DescribeTo(::std::ostream* const os) const override { - *os << StreamFormat("%s", DumpEvents(references_, 1)); - } - - void DescribeNegationTo(::std::ostream* const os) const override { - *os << StreamFormat("mismatch from %s", DumpEvents(references_, 1)); - } - - private: - std::vector<Event> references_; -}; - -::testing::Matcher<std::vector<Event>> Are(std::vector<Event> events) { - return MakeMatcher(new EventsAreMatcher(std::move(events))); -} - -// Similar to the EventsAre matcher, but the order of events are ignored. -class UnorderedEventsAreMatcher - : public ::testing::MatcherInterface<std::vector<Event>> { - public: - explicit UnorderedEventsAreMatcher(std::vector<Event> references) - : references_(std::move(references)) {} - - bool MatchAndExplain( - std::vector<Event> events, - ::testing::MatchResultListener* const listener) const override { - if (references_.size() != events.size()) { - *listener << StreamFormat("\n\tCount mismatch, got %s", - DumpEvents(events, 2)); - return false; - } - - std::vector<Event> unmatched(references_); - - for (const Event& candidate : events) { - for (auto it = unmatched.begin(); it != unmatched.end();) { - const Event& reference = *it; - if (candidate.mask == reference.mask && candidate.wd == reference.wd && - candidate.name == reference.name && - candidate.cookie == reference.cookie) { - it = unmatched.erase(it); - break; - } else { - ++it; - } - } - } - - // Anything left unmatched? If so, the matcher fails. - if (!unmatched.empty()) { - *listener << StreamFormat("\n\tFailed to match %s", - DumpEvents(unmatched, 2)); - *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2)); - return false; - } - - return true; - } - - void DescribeTo(::std::ostream* const os) const override { - *os << StreamFormat("unordered %s", DumpEvents(references_, 1)); - } - - void DescribeNegationTo(::std::ostream* const os) const override { - *os << StreamFormat("mismatch from unordered %s", - DumpEvents(references_, 1)); - } - - private: - std::vector<Event> references_; -}; - -::testing::Matcher<std::vector<Event>> AreUnordered(std::vector<Event> events) { - return MakeMatcher(new UnorderedEventsAreMatcher(std::move(events))); -} - -// Reads events from an inotify fd until either EOF, or read returns EAGAIN. -PosixErrorOr<std::vector<Event>> DrainEvents(int fd) { - std::vector<Event> events; - while (true) { - int events_size = 0; - if (ioctl(fd, FIONREAD, &events_size) < 0) { - return PosixError(errno, "ioctl(FIONREAD) failed on inotify fd"); - } - // Deliberately use a buffer that is larger than necessary, expecting to - // only read events_size bytes. - std::vector<char> buf(events_size + kBufSize, 0); - const ssize_t readlen = read(fd, buf.data(), buf.size()); - MaybeSave(); - // Read error? - if (readlen < 0) { - if (errno == EAGAIN) { - // If EAGAIN, no more events at the moment. Return what we have so far. - return events; - } - // Some other read error. Return an error. Right now if we encounter this - // after already reading some events, they get lost. However, we don't - // expect to see any error, and the calling test will fail immediately if - // we signal an error anyways, so this is acceptable. - return PosixError(errno, "read() failed on inotify fd"); - } - if (readlen < static_cast<int>(sizeof(struct inotify_event))) { - // Impossibly short read. - return PosixError( - EIO, - "read() didn't return enough data represent even a single event"); - } - if (readlen != events_size) { - return PosixError(EINVAL, absl::StrCat("read ", readlen, - " bytes, expected ", events_size)); - } - if (readlen == 0) { - // EOF. - return events; - } - - // Normal read. - const char* cursor = buf.data(); - while (cursor < (buf.data() + readlen)) { - struct inotify_event event = {}; - memcpy(&event, cursor, sizeof(struct inotify_event)); - - Event ev; - ev.wd = event.wd; - ev.mask = event.mask; - ev.cookie = event.cookie; - ev.len = event.len; - if (event.len > 0) { - TEST_CHECK(static_cast<int>(sizeof(struct inotify_event) + event.len) <= - readlen); - ev.name = std::string(cursor + - offsetof(struct inotify_event, name)); // NOLINT - // Name field should always be smaller than event.len, otherwise we have - // a buffer overflow. The two sizes aren't equal because the string - // constructor will stop at the first null byte, while event.name may be - // padded up to event.len using multiple null bytes. - TEST_CHECK(ev.name.size() <= event.len); - } - - events.push_back(ev); - cursor += sizeof(struct inotify_event) + event.len; - } - } -} - -PosixErrorOr<FileDescriptor> InotifyInit1(int flags) { - int fd = inotify_init1(flags); - if (fd < 0) { - return PosixError(errno, "inotify_init1() failed"); - } - return FileDescriptor(fd); -} - -PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path, - uint32_t mask) { - int wd = inotify_add_watch(fd, path.c_str(), mask); - if (wd < 0) { - return PosixError(errno, "inotify_add_watch() failed"); - } - return wd; -} - -TEST(Inotify, IllegalSeek) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0)); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); -} - -TEST(Inotify, IllegalPread) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0)); - int val; - EXPECT_THAT(pread(fd.get(), &val, sizeof(val), 0), - SyscallFailsWithErrno(ESPIPE)); -} - -TEST(Inotify, IllegalPwrite) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0)); - EXPECT_THAT(pwrite(fd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE)); -} - -TEST(Inotify, IllegalWrite) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0)); - int val = 0; - EXPECT_THAT(write(fd.get(), &val, sizeof(val)), SyscallFailsWithErrno(EBADF)); -} - -TEST(Inotify, InitFlags) { - EXPECT_THAT(inotify_init1(IN_NONBLOCK | IN_CLOEXEC), SyscallSucceeds()); - EXPECT_THAT(inotify_init1(12345), SyscallFailsWithErrno(EINVAL)); -} - -TEST(Inotify, NonBlockingReadReturnsEagain) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - std::vector<char> buf(kBufSize, 0); - - // The read below should return fail with EAGAIN because there is no data to - // read and we've specified IN_NONBLOCK. We're guaranteed that there is no - // data to read because we haven't registered any watches yet. - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(Inotify, AddWatchOnInvalidFdFails) { - // Garbage fd. - EXPECT_THAT(inotify_add_watch(-1, "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(inotify_add_watch(1337, "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EBADF)); - - // Non-inotify fds. - EXPECT_THAT(inotify_add_watch(0, "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(inotify_add_watch(1, "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(inotify_add_watch(2, "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EINVAL)); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY)); - EXPECT_THAT(inotify_add_watch(fd.get(), "/tmp", IN_ALL_EVENTS), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(Inotify, RemovingWatchGeneratesEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds()); - - // Read events, ensure the first event is IN_IGNORED. - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_IGNORED, wd)})); -} - -TEST(Inotify, CanDeleteFileAfterRemovingWatch) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds()); - file1.reset(); -} - -TEST(Inotify, RemoveWatchAfterDeletingFileFails) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - file1.reset(); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd), Event(IN_DELETE_SELF, wd), - Event(IN_IGNORED, wd)})); - - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL)); -} - -TEST(Inotify, DuplicateWatchRemovalFails) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds()); - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL)); -} - -TEST(Inotify, ConcurrentFileDeletionAndWatchRemoval) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const std::string filename = NewTempAbsPathInDir(root.path()); - - auto file_create_delete = [filename]() { - const DisableSave ds; // Too expensive. - for (int i = 0; i < 100; ++i) { - FileDescriptor file_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT, S_IRUSR | S_IWUSR)); - // Close before unlinking (although S/R is disabled). Some filesystems - // cannot restore an open fd on an unlinked file. - file_fd.reset(); - EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds()); - } - }; - - const int shared_fd = fd.get(); // We need to pass it to the thread. - auto add_remove_watch = [shared_fd, filename]() { - for (int i = 0; i < 100; ++i) { - int wd = inotify_add_watch(shared_fd, filename.c_str(), IN_ALL_EVENTS); - MaybeSave(); - if (wd != -1) { - // Watch added successfully, try removal. - if (inotify_rm_watch(shared_fd, wd)) { - // If removal fails, the only acceptable reason is if the wd - // is invalid, which will be the case if we try to remove - // the watch after the file has been deleted. - EXPECT_EQ(errno, EINVAL); - } - } else { - // Add watch failed, this should only fail if the target file doesn't - // exist. - EXPECT_EQ(errno, ENOENT); - } - } - }; - - ScopedThread t1(file_create_delete); - ScopedThread t2(add_remove_watch); -} - -TEST(Inotify, DeletingChildGeneratesEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - const std::string file1_path = file1.reset(); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd), - Event(IN_IGNORED, file1_wd), - Event(IN_DELETE, root_wd, Basename(file1_path))})); -} - -// Creating a file in "parent/child" should generate events for child, but not -// parent. -TEST(Inotify, CreatingFileGeneratesEvents) { - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath child = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS)); - - // Create a new file in the directory. - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(child.path())); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - // The library function we use to create the new file opens it for writing to - // create it and sets permissions on it, so we expect the three extra events. - ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path())), - Event(IN_OPEN, wd, Basename(file1.path())), - Event(IN_CLOSE_WRITE, wd, Basename(file1.path())), - Event(IN_ATTRIB, wd, Basename(file1.path()))})); -} - -TEST(Inotify, ReadingFileGeneratesAccessEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - char buf; - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))})); -} - -TEST(Inotify, WritingFileGeneratesModifyEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - const std::string data = "some content"; - EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()), - SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_MODIFY, wd, Basename(file1.path()))})); -} - -TEST(Inotify, SizeZeroReadWriteGeneratesNothing) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - // Read from the empty file. - int val; - ASSERT_THAT(read(file1_fd.get(), &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - - // Write zero bytes. - ASSERT_THAT(write(file1_fd.get(), "", 0), SyscallSucceedsWithValue(0)); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({})); -} - -TEST(Inotify, FailedFileCreationGeneratesNoEvents) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string dir_path = dir.path(); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(fd.get(), dir_path, IN_ALL_EVENTS)); - - const char* p = dir_path.c_str(); - ASSERT_THAT(mkdir(p, 0777), SyscallFails()); - ASSERT_THAT(mknod(p, S_IFIFO, 0777), SyscallFails()); - ASSERT_THAT(symlink(p, p), SyscallFails()); - ASSERT_THAT(link(p, p), SyscallFails()); - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({})); -} - -TEST(Inotify, WatchSetAfterOpenReportsCloseFdEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - FileDescriptor file1_fd_writable = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - FileDescriptor file1_fd_not_writable = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - file1_fd_writable.reset(); // Close file1_fd_writable. - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd, Basename(file1.path()))})); - - file1_fd_not_writable.reset(); // Close file1_fd_not_writable. - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, - Are({Event(IN_CLOSE_NOWRITE, wd, Basename(file1.path()))})); -} - -TEST(Inotify, ChildrenDeletionInWatchedDirGeneratesEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - const std::string file1_path = file1.reset(); - const std::string dir1_path = dir1.release(); - EXPECT_THAT(rmdir(dir1_path.c_str()), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - ASSERT_THAT(events, - Are({Event(IN_DELETE, wd, Basename(file1_path)), - Event(IN_DELETE | IN_ISDIR, wd, Basename(dir1_path))})); -} - -TEST(Inotify, RmdirOnWatchedTargetGeneratesEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - EXPECT_THAT(rmdir(root.path().c_str()), SyscallSucceeds()); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_DELETE_SELF, wd), Event(IN_IGNORED, wd)})); -} - -TEST(Inotify, MoveGeneratesEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const TempPath dir1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - const TempPath dir2 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS)); - const int dir2_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), dir2.path(), IN_ALL_EVENTS)); - // Test move from root -> root. - std::string newpath = NewTempAbsPathInDir(root.path()); - std::string oldpath = file1.release(); - EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds()); - file1.reset(newpath); - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie), - Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie)})); - EXPECT_NE(events[0].cookie, 0); - EXPECT_EQ(events[0].cookie, events[1].cookie); - uint32_t last_cookie = events[0].cookie; - - // Test move from root -> root/dir1. - newpath = NewTempAbsPathInDir(dir1.path()); - oldpath = file1.release(); - EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds()); - file1.reset(newpath); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie), - Event(IN_MOVED_TO, dir1_wd, Basename(newpath), events[1].cookie)})); - // Cookies should be distinct between distinct rename events. - EXPECT_NE(events[0].cookie, last_cookie); - EXPECT_EQ(events[0].cookie, events[1].cookie); - last_cookie = events[0].cookie; - - // Test move from root/dir1 -> root/dir2. - newpath = NewTempAbsPathInDir(dir2.path()); - oldpath = file1.release(); - EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds()); - file1.reset(newpath); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_MOVED_FROM, dir1_wd, Basename(oldpath), events[0].cookie), - Event(IN_MOVED_TO, dir2_wd, Basename(newpath), events[1].cookie)})); - EXPECT_NE(events[0].cookie, last_cookie); - EXPECT_EQ(events[0].cookie, events[1].cookie); - last_cookie = events[0].cookie; -} - -TEST(Inotify, MoveWatchedTargetGeneratesEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - const std::string newpath = NewTempAbsPathInDir(root.path()); - const std::string oldpath = file1.release(); - EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds()); - file1.reset(newpath); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie), - Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie), - // Self move events do not have a cookie. - Event(IN_MOVE_SELF, file1_wd)})); - EXPECT_NE(events[0].cookie, 0); - EXPECT_EQ(events[0].cookie, events[1].cookie); -} - -// Tests that close events are only emitted when a file description drops its -// last reference. -TEST(Inotify, DupFD) { - SKIP_IF(IsRunningWithVFS1()); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); - - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_OPEN, wd), - })); - - fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); - - fd2.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_CLOSE_NOWRITE, wd), - })); -} - -TEST(Inotify, CoalesceEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - // Read the file a few times. This will would generate multiple IN_ACCESS - // events but they should get coalesced to a single event. - char buf; - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - // Use the close event verify that we haven't simply left the additional - // IN_ACCESS events unread. - file1_fd.reset(); // Close file1_fd. - - const std::string file1_name = std::string(Basename(file1.path())); - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, file1_name), - Event(IN_CLOSE_NOWRITE, wd, file1_name)})); - - // Now let's try interleaving other events into a stream of repeated events. - file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds()); - EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds()); - EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - file1_fd.reset(); // Close the file. - - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_OPEN, wd, file1_name), Event(IN_ACCESS, wd, file1_name), - Event(IN_MODIFY, wd, file1_name), Event(IN_ACCESS, wd, file1_name), - Event(IN_CLOSE_WRITE, wd, file1_name)})); - - // Ensure events aren't coalesced if they are from different files. - const TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - // Discard events resulting from creation of file2. - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - FileDescriptor file2_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file2.path(), O_RDONLY)); - - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file2_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - // Close both files. - file1_fd.reset(); - file2_fd.reset(); - - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - const std::string file2_name = std::string(Basename(file2.path())); - ASSERT_THAT( - events, - Are({Event(IN_OPEN, wd, file1_name), Event(IN_OPEN, wd, file2_name), - Event(IN_ACCESS, wd, file1_name), Event(IN_ACCESS, wd, file2_name), - Event(IN_ACCESS, wd, file1_name), - Event(IN_CLOSE_NOWRITE, wd, file1_name), - Event(IN_CLOSE_NOWRITE, wd, file2_name)})); -} - -TEST(Inotify, ClosingInotifyFdWithoutRemovingWatchesWorks) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - // Note: The check on close will happen in FileDescriptor::~FileDescriptor(). -} - -TEST(Inotify, NestedWatches) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - // Read from file1. This should generate an event for both watches. - char buf; - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ACCESS, root_wd, Basename(file1.path())), - Event(IN_ACCESS, file1_wd)})); -} - -TEST(Inotify, ConcurrentThreadsGeneratingEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - std::vector<TempPath> files; - files.reserve(10); - for (int i = 0; i < 10; i++) { - files.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode))); - } - - auto test_thread = [&files]() { - uint32_t seed = time(nullptr); - for (int i = 0; i < 20; i++) { - const TempPath& file = files[rand_r(&seed) % files.size()]; - const FileDescriptor file_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - TEST_PCHECK(write(file_fd.get(), "x", 1) == 1); - } - }; - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - std::list<ScopedThread> threads; - for (int i = 0; i < 3; i++) { - threads.emplace_back(test_thread); - } - for (auto& t : threads) { - t.Join(); - } - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - // 3 threads doing 20 iterations, 3 events per iteration (open, write, - // close). However, some events may be coalesced, and we can't reliably - // predict how they'll be coalesced since the test threads aren't - // synchronized. We can only check that we aren't getting unexpected events. - for (const Event& ev : events) { - EXPECT_NE(ev.mask & (IN_OPEN | IN_MODIFY | IN_CLOSE_WRITE), 0); - } -} - -TEST(Inotify, ReadWithTooSmallBufferFails) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - // Open the file to queue an event. This event will not have a filename, so - // reading from the inotify fd should return sizeof(struct inotify_event) - // bytes of data. - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - std::vector<char> buf(kBufSize, 0); - ssize_t readlen; - - // Try a buffer too small to hold any potential event. This is rejected - // outright without the event being dequeued. - EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1), - SyscallFailsWithErrno(EINVAL)); - // Try a buffer just large enough. This should succeeed. - EXPECT_THAT( - readlen = read(fd.get(), buf.data(), sizeof(struct inotify_event)), - SyscallSucceeds()); - EXPECT_EQ(readlen, sizeof(struct inotify_event)); - // Event queue is now empty, the next read should return EAGAIN. - EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)), - SyscallFailsWithErrno(EAGAIN)); - - // Now put a watch on the directory, so that generated events contain a name. - EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds()); - - // Drain the event generated from the watch removal. - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - file1_fd.reset(); // Close file to generate an event. - - // Try a buffer too small to hold any event and one too small to hold an event - // with a name. These should both fail without consuming the event. - EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)), - SyscallFailsWithErrno(EINVAL)); - // Now try with a large enough buffer. This should return the one event. - EXPECT_THAT(readlen = read(fd.get(), buf.data(), buf.size()), - SyscallSucceeds()); - EXPECT_GE(readlen, - sizeof(struct inotify_event) + Basename(file1.path()).size()); - // With the single event read, the queue should once again be empty. - EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(Inotify, BlockingReadOnInotifyFd) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0)); - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - // Spawn a thread performing a blocking read for new events on the inotify fd. - std::vector<char> buf(kBufSize, 0); - const int shared_fd = fd.get(); // The thread needs it. - ScopedThread t([shared_fd, &buf]() { - ssize_t readlen; - EXPECT_THAT(readlen = read(shared_fd, buf.data(), buf.size()), - SyscallSucceeds()); - }); - - // Perform a read on the watched file, which should generate an IN_ACCESS - // event, unblocking the event_reader thread. - char c; - EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds()); - - // Wait for the thread to read the event and exit. - t.Join(); - - // Make sure the event we got back is sane. - uint32_t event_mask; - memcpy(&event_mask, buf.data() + offsetof(struct inotify_event, mask), - sizeof(event_mask)); - EXPECT_EQ(event_mask, IN_ACCESS); -} - -TEST(Inotify, WatchOnRelativePath) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - - // Change working directory to root. - const FileDescriptor cwd = ASSERT_NO_ERRNO_AND_VALUE(Open(".", O_PATH)); - EXPECT_THAT(chdir(root.path().c_str()), SyscallSucceeds()); - - // Add a watch on file1 with a relative path. - const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - fd.get(), std::string(Basename(file1.path())), IN_ALL_EVENTS)); - - // Perform a read on file1, this should generate an IN_ACCESS event. - char c; - EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_ACCESS, wd)})); - - // Explicitly reset the working directory so that we don't continue to - // reference "root". Once the test ends, "root" will get unlinked. If we - // continue to hold a reference, random save/restore tests can fail if a save - // is triggered after "root" is unlinked; we can't save deleted fs objects - // with active references. - EXPECT_THAT(fchdir(cwd.get()), SyscallSucceeds()); -} - -TEST(Inotify, ZeroLengthReadWriteDoesNotGenerateEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const char kContent[] = "some content"; - TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), kContent, TempPath::kDefaultFileMode)); - const int kContentSize = sizeof(kContent) - 1; - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - std::vector<char> buf(kContentSize, 0); - // Read all available data. - ssize_t readlen; - EXPECT_THAT(readlen = read(file1_fd.get(), buf.data(), kContentSize), - SyscallSucceeds()); - EXPECT_EQ(readlen, kContentSize); - // Drain all events and make sure we got the IN_ACCESS for the read. - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))})); - - // Now try read again. This should be a 0-length read, since we're at EOF. - char c; - EXPECT_THAT(readlen = read(file1_fd.get(), &c, 1), SyscallSucceeds()); - EXPECT_EQ(readlen, 0); - // We should have no new events. - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_TRUE(events.empty()); - - // Try issuing a zero-length read. - EXPECT_THAT(readlen = read(file1_fd.get(), &c, 0), SyscallSucceeds()); - EXPECT_EQ(readlen, 0); - // We should have no new events. - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_TRUE(events.empty()); - - // Try issuing a zero-length write. - ssize_t writelen; - EXPECT_THAT(writelen = write(file1_fd.get(), &c, 0), SyscallSucceeds()); - EXPECT_EQ(writelen, 0); - // We should have no new events. - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_TRUE(events.empty()); -} - -TEST(Inotify, ChmodGeneratesAttribEvent_NoRandomSave) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - FileDescriptor root_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(root.path(), O_RDONLY)); - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - auto verify_chmod_events = [&]() { - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ATTRIB, root_wd, Basename(file1.path())), - Event(IN_ATTRIB, file1_wd)})); - }; - - // Don't do cooperative S/R tests for any of the {f}chmod* syscalls below, the - // test will always fail because nodes cannot be saved when they have stricter - // permissions than the original host node. - const DisableSave ds; - - // Chmod. - ASSERT_THAT(chmod(file1.path().c_str(), S_IWGRP), SyscallSucceeds()); - verify_chmod_events(); - - // Fchmod. - ASSERT_THAT(fchmod(file1_fd.get(), S_IRGRP | S_IWGRP), SyscallSucceeds()); - verify_chmod_events(); - - // Fchmodat. - const std::string file1_basename = std::string(Basename(file1.path())); - ASSERT_THAT(fchmodat(root_fd.get(), file1_basename.c_str(), S_IWGRP, 0), - SyscallSucceeds()); - verify_chmod_events(); - - // Make sure the chmod'ed file descriptors are destroyed before DisableSave - // is destructed. - root_fd.reset(); - file1_fd.reset(); -} - -TEST(Inotify, TruncateGeneratesModifyEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - auto verify_truncate_events = [&]() { - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_MODIFY, root_wd, Basename(file1.path())), - Event(IN_MODIFY, file1_wd)})); - }; - - // Truncate. - EXPECT_THAT(truncate(file1.path().c_str(), 4096), SyscallSucceeds()); - verify_truncate_events(); - - // Ftruncate. - EXPECT_THAT(ftruncate(file1_fd.get(), 8192), SyscallSucceeds()); - verify_truncate_events(); - - // No events if truncate fails. - EXPECT_THAT(ftruncate(file1_fd.get(), -1), SyscallFailsWithErrno(EINVAL)); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({})); -} - -TEST(Inotify, GetdentsGeneratesAccessEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - // This internally calls getdents(2). We also expect to see an open/close - // event for the dirfd. - ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false)); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - // Linux only seems to generate access events on getdents() on some - // calls. Allow the test to pass even if it isn't generated. gVisor will - // always generate the IN_ACCESS event so the test will at least ensure gVisor - // behaves reasonably. - int i = 0; - EXPECT_EQ(events[i].mask, IN_OPEN | IN_ISDIR); - ++i; - if (IsRunningOnGvisor()) { - EXPECT_EQ(events[i].mask, IN_ACCESS | IN_ISDIR); - ++i; - } else { - if (events[i].mask == (IN_ACCESS | IN_ISDIR)) { - // Skip over the IN_ACCESS event on Linux, it only shows up some of the - // time so we can't assert its existence. - ++i; - } - } - EXPECT_EQ(events[i].mask, IN_CLOSE_NOWRITE | IN_ISDIR); -} - -TEST(Inotify, MknodGeneratesCreateEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - const TempPath file1(root.path() + "/file1"); - ASSERT_THAT(mknod(file1.path().c_str(), S_IFREG, 0), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path()))})); -} - -TEST(Inotify, SymlinkGeneratesCreateEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const TempPath link1(NewTempAbsPathInDir(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - ASSERT_THAT(symlink(file1.path().c_str(), link1.path().c_str()), - SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - - ASSERT_THAT(events, Are({Event(IN_CREATE, root_wd, Basename(link1.path()))})); -} - -TEST(Inotify, LinkGeneratesAttribAndCreateEvents) { - // Inotify does not work properly with hard links in gofer and overlay fs. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); - - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const TempPath link1(root.path() + "/link1"); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - ASSERT_THAT(link(file1.path().c_str(), link1.path().c_str()), - SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ATTRIB, file1_wd), - Event(IN_CREATE, root_wd, Basename(link1.path()))})); -} - -TEST(Inotify, UtimesGeneratesAttribEvent) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - - const struct timeval times[2] = {{1, 0}, {2, 0}}; - EXPECT_THAT(futimes(file1_fd.get(), times), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file1.path()))})); -} - -TEST(Inotify, HardlinksReuseSameWatch) { - // Inotify does not work properly with hard links in gofer and overlay fs. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); - - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - TempPath file2(root.path() + "/file2"); - ASSERT_THAT(link(file.path().c_str(), file2.path().c_str()), - SyscallSucceeds()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int root_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int file_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file.path(), IN_ALL_EVENTS)); - const int file2_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file2.path(), IN_ALL_EVENTS)); - - // The watch descriptors for watches on different links to the same file - // should be identical. - EXPECT_NE(root_wd, file_wd); - EXPECT_EQ(file_wd, file2_wd); - - FileDescriptor file_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, - AreUnordered({Event(IN_OPEN, root_wd, Basename(file.path())), - Event(IN_OPEN, file_wd)})); - - // For the next step, we want to ensure all fds to the file are closed. Do - // that now and drain the resulting events. - file_fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - AreUnordered({Event(IN_CLOSE_WRITE, root_wd, Basename(file.path())), - Event(IN_CLOSE_WRITE, file_wd)})); - - // Try removing the link and let's see what events show up. Note that after - // this, we still have a link to the file so the watch shouldn't be - // automatically removed. - const std::string file2_path = file2.reset(); - - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, - AreUnordered({Event(IN_ATTRIB, file2_wd), - Event(IN_DELETE, root_wd, Basename(file2_path))})); - - // Now remove the other link. Since this is the last link to the file, the - // watch should be automatically removed. - const std::string file_path = file.reset(); - - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - AreUnordered({Event(IN_ATTRIB, file_wd), Event(IN_DELETE_SELF, file_wd), - Event(IN_IGNORED, file_wd), - Event(IN_DELETE, root_wd, Basename(file_path))})); -} - -// Calling mkdir within "parent/child" should generate an event for child, but -// not parent. -TEST(Inotify, MkdirGeneratesCreateEventWithDirFlag) { - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath child = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS)); - const int child_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS)); - - const TempPath dir1(NewTempAbsPathInDir(child.path())); - ASSERT_THAT(mkdir(dir1.path().c_str(), 0777), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT( - events, - Are({Event(IN_CREATE | IN_ISDIR, child_wd, Basename(dir1.path()))})); -} - -TEST(Inotify, MultipleInotifyInstancesAndWatchesAllGetEvents) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - constexpr int kNumFds = 30; - std::vector<FileDescriptor> inotify_fds; - - for (int i = 0; i < kNumFds; ++i) { - const DisableSave ds; // Too expensive. - inotify_fds.emplace_back( - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK))); - const FileDescriptor& fd = inotify_fds[inotify_fds.size() - 1]; // Back. - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - } - - const std::string data = "some content"; - EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()), - SyscallSucceeds()); - - for (const FileDescriptor& fd : inotify_fds) { - const DisableSave ds; // Too expensive. - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - if (events.size() >= 2) { - EXPECT_EQ(events[0].mask, IN_MODIFY); - EXPECT_EQ(events[0].wd, 1); - EXPECT_EQ(events[0].name, Basename(file1.path())); - EXPECT_EQ(events[1].mask, IN_MODIFY); - EXPECT_EQ(events[1].wd, 2); - EXPECT_EQ(events[1].name, ""); - } - } -} - -TEST(Inotify, EventsGoUpAtMostOneLevel) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath dir1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS)); - const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS)); - - const std::string file1_path = file1.reset(); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_DELETE, dir1_wd, Basename(file1_path))})); -} - -TEST(Inotify, DuplicateWatchReturnsSameWatchDescriptor) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd1 = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - const int wd2 = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - EXPECT_EQ(wd1, wd2); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - // The watch shouldn't be duplicated, we only expect one event. - ASSERT_THAT(events, Are({Event(IN_OPEN, wd1)})); -} - -TEST(Inotify, UnmatchedEventsAreDiscarded) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ACCESS)); - - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - // We only asked for access events, the open event should be discarded. - ASSERT_THAT(events, Are({})); - - // IN_IGNORED events are always generated, regardless of the mask. - file1_fd.reset(); - file1.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_IGNORED, wd)})); -} - -TEST(Inotify, AddWatchWithInvalidEventMaskFails) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - EXPECT_THAT(inotify_add_watch(fd.get(), root.path().c_str(), 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(Inotify, AddWatchOnInvalidPathFails) { - const TempPath nonexistent(NewTempAbsPath()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - // Non-existent path. - EXPECT_THAT( - inotify_add_watch(fd.get(), nonexistent.path().c_str(), IN_CREATE), - SyscallFailsWithErrno(ENOENT)); - - // Garbage path pointer. - EXPECT_THAT(inotify_add_watch(fd.get(), nullptr, IN_CREATE), - SyscallFailsWithErrno(EFAULT)); -} - -TEST(Inotify, InOnlyDirFlagRespected) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - EXPECT_THAT( - inotify_add_watch(fd.get(), root.path().c_str(), IN_ACCESS | IN_ONLYDIR), - SyscallSucceeds()); - - EXPECT_THAT( - inotify_add_watch(fd.get(), file1.path().c_str(), IN_ACCESS | IN_ONLYDIR), - SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(Inotify, MaskAddMergesWithExistingEventMask) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path())); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_OPEN | IN_CLOSE_WRITE)); - - const std::string data = "some content"; - EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()), - SyscallSucceeds()); - - // We shouldn't get any events, since IN_MODIFY wasn't in the event mask. - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({})); - - // Add IN_MODIFY to event mask. - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_MODIFY | IN_MASK_ADD)); - - EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()), - SyscallSucceeds()); - - // This time we should get the modify event. - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_MODIFY, wd)})); - - // Now close the fd. If the modify event was added to the event mask rather - // than replacing the event mask we won't get the close event. - file1_fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd)})); -} - -// Test that control events bits are not considered when checking event mask. -TEST(Inotify, ControlEvents) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), dir.path(), IN_ACCESS)); - - // Check that events in the mask are dispatched and that control bits are - // part of the event mask. - std::vector<std::string> files = - ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false)); - ASSERT_EQ(files.size(), 2); - - const std::vector<Event> events1 = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events1, Are({Event(IN_ACCESS | IN_ISDIR, wd)})); - - // Check that events not in the mask are discarded. - const FileDescriptor dir_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY)); - - const std::vector<Event> events2 = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - ASSERT_THAT(events2, Are({})); -} - -// Regression test to ensure epoll and directory access doesn't deadlock. -TEST(Inotify, EpollNoDeadlock) { - const DisableSave ds; // Too many syscalls. - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - // Create lots of directories and watch all of them. - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::vector<TempPath> children; - for (size_t i = 0; i < 1000; ++i) { - auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), child.path(), IN_ACCESS)); - children.emplace_back(std::move(child)); - } - - // Run epoll_wait constantly in a separate thread. - std::atomic<bool> done(false); - ScopedThread th([&fd, &done] { - for (auto start = absl::Now(); absl::Now() - start < absl::Seconds(5);) { - FileDescriptor epoll_fd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - ASSERT_NO_ERRNO(RegisterEpollFD(epoll_fd.get(), fd.get(), - EPOLLIN | EPOLLOUT | EPOLLET, 0)); - struct epoll_event result[1]; - EXPECT_THAT(RetryEINTR(epoll_wait)(epoll_fd.get(), result, 1, -1), - SyscallSucceedsWithValue(1)); - - sched_yield(); - } - done = true; - }); - - // While epoll thread is running, constantly access all directories to - // generate inotify events. - while (!done) { - std::vector<std::string> files = - ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false)); - ASSERT_EQ(files.size(), 1002); - for (const auto& child : files) { - if (child == "." || child == "..") { - continue; - } - ASSERT_NO_ERRNO_AND_VALUE(ListDir(JoinPath(root.path(), child), false)); - } - sched_yield(); - } -} - -TEST(Inotify, Fallocate) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - - // Do an arbitrary modification with fallocate. - ASSERT_THAT(RetryEINTR(fallocate)(fd.get(), 0, 0, 123), SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)})); -} - -TEST(Inotify, Utimensat) { - 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_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - - // Just update the access time. - struct timespec times[2] = {}; - times[0].tv_nsec = UTIME_NOW; - times[1].tv_nsec = UTIME_OMIT; - ASSERT_THAT(RetryEINTR(utimensat)(AT_FDCWD, file.path().c_str(), times, 0), - SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ACCESS, wd)})); - - // Just the modify time. - times[0].tv_nsec = UTIME_OMIT; - times[1].tv_nsec = UTIME_NOW; - ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), times, 0), - SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)})); - - // Both together. - times[0].tv_nsec = UTIME_NOW; - times[1].tv_nsec = UTIME_NOW; - ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), times, 0), - SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); -} - -TEST(Inotify, Sendfile) { - SKIP_IF(IsRunningWithVFS1()); - - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(root.path(), "x", 0644)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - const FileDescriptor out = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Create separate inotify instances for the in and out fds. If both watches - // were on the same instance, we would have discrepancies between Linux and - // gVisor (order of events, duplicate events), which is not that important - // since inotify is asynchronous anyway. - const FileDescriptor in_inotify = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const FileDescriptor out_inotify = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int in_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(in_inotify.get(), in_file.path(), IN_ALL_EVENTS)); - const int out_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(out_inotify.get(), out_file.path(), IN_ALL_EVENTS)); - - ASSERT_THAT(sendfile(out.get(), in.get(), /*offset=*/nullptr, 1), - SyscallSucceeds()); - - // Expect a single access event and a single modify event. - std::vector<Event> in_events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(in_inotify.get())); - std::vector<Event> out_events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(out_inotify.get())); - EXPECT_THAT(in_events, Are({Event(IN_ACCESS, in_wd)})); - EXPECT_THAT(out_events, Are({Event(IN_MODIFY, out_wd)})); -} - -TEST(Inotify, SpliceOnWatchTarget) { - int pipefds[2]; - ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS)); - const int file_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - - EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, 1, /*flags=*/0), - SyscallSucceedsWithValue(1)); - - // Surprisingly, events may not be generated in Linux if we read from a file. - // fs/splice.c:generic_file_splice_read, which is used most often, does not - // generate events, whereas fs/splice.c:default_file_splice_read does. - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - if (IsRunningOnGvisor() && !IsRunningWithVFS1()) { - ASSERT_THAT(events, Are({Event(IN_ACCESS, dir_wd, Basename(file.path())), - Event(IN_ACCESS, file_wd)})); - } - - EXPECT_THAT(splice(pipefds[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0), - SyscallSucceedsWithValue(1)); - - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - ASSERT_THAT(events, Are({ - Event(IN_MODIFY, dir_wd, Basename(file.path())), - Event(IN_MODIFY, file_wd), - })); -} - -TEST(Inotify, SpliceOnInotifyFD) { - int pipefds[2]; - ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); - - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - const int watcher = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - char buf; - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, - sizeof(struct inotify_event) + 1, SPLICE_F_NONBLOCK), - SyscallSucceedsWithValue(sizeof(struct inotify_event))); - - const FileDescriptor read_fd(pipefds[0]); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(read_fd.get())); - ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)})); -} - -// Watches on a parent should not be triggered by actions on a hard link to one -// of its children that has a different parent. -TEST(Inotify, LinkOnOtherParent) { - // Inotify does not work properly with hard links in gofer and overlay fs. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); - - const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - std::string link_path = NewTempAbsPathInDir(dir2.path()); - - ASSERT_THAT(link(file.path().c_str(), link_path.c_str()), SyscallSucceeds()); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), dir1.path(), IN_ALL_EVENTS)); - - // Perform various actions on the link outside of dir1, which should trigger - // no inotify events. - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR)); - int val = 0; - ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds()); - - // Close before unlinking; some filesystems cannot restore an open fd on an - // unlinked file. - fd.reset(); - ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); -} - -TEST(Inotify, Xattr) { - // TODO(gvisor.dev/issue/1636): Support extended attributes in runsc gofer. - SKIP_IF(IsRunningOnGvisor()); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string path = file.path(); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDWR)); - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), path, IN_ALL_EVENTS)); - - const char* cpath = path.c_str(); - const char* name = "user.test"; - int val = 123; - ASSERT_THAT(setxattr(cpath, name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); - - ASSERT_THAT(getxattr(cpath, name, &val, sizeof(val)), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); - - char list[100]; - ASSERT_THAT(listxattr(cpath, list, sizeof(list)), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); - - ASSERT_THAT(removexattr(cpath, name), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); - - ASSERT_THAT(fsetxattr(fd.get(), name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); - - ASSERT_THAT(fgetxattr(fd.get(), name, &val, sizeof(val)), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); - - ASSERT_THAT(flistxattr(fd.get(), list, sizeof(list)), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({})); - - ASSERT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)})); -} - -TEST(Inotify, Exec) { - SKIP_IF(IsRunningWithVFS1()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), "/bin/true", IN_ALL_EVENTS)); - - // Perform exec. - pid_t child = -1; - int execve_errno = -1; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/bin/true", {}, {}, nullptr, &child, &execve_errno)); - ASSERT_EQ(0, execve_errno); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); - EXPECT_EQ(0, status); - - // Process cleanup no longer needed. - kill.Release(); - - std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd), - Event(IN_CLOSE_NOWRITE, wd)})); -} - -// Watches without IN_EXCL_UNLINK, should continue to emit events for file -// descriptors after their corresponding files have been unlinked. -// -// We need to disable S/R because there are filesystems where we cannot re-open -// fds to an unlinked file across S/R, e.g. gofer-backed filesytems. -TEST(Inotify, IncludeUnlinkedFile_NoRandomSave) { - const DisableSave ds; - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(dir.path(), "123", TempPath::kDefaultFileMode)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS)); - const int file_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - - ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds()); - int val = 0; - ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, AnyOf(Are({ - Event(IN_ATTRIB, file_wd), - Event(IN_DELETE, dir_wd, Basename(file.path())), - Event(IN_ACCESS, dir_wd, Basename(file.path())), - Event(IN_ACCESS, file_wd), - Event(IN_MODIFY, dir_wd, Basename(file.path())), - Event(IN_MODIFY, file_wd), - }), - Are({ - Event(IN_DELETE, dir_wd, Basename(file.path())), - Event(IN_ATTRIB, file_wd), - Event(IN_ACCESS, dir_wd, Basename(file.path())), - Event(IN_ACCESS, file_wd), - Event(IN_MODIFY, dir_wd, Basename(file.path())), - Event(IN_MODIFY, file_wd), - }))); - - fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_CLOSE_WRITE, dir_wd, Basename(file.path())), - Event(IN_CLOSE_WRITE, file_wd), - Event(IN_DELETE_SELF, file_wd), - Event(IN_IGNORED, file_wd), - })); -} - -// Watches created with IN_EXCL_UNLINK will stop emitting events on fds for -// children that have already been unlinked. -// -// We need to disable S/R because there are filesystems where we cannot re-open -// fds to an unlinked file across S/R, e.g. gofer-backed filesytems. -TEST(Inotify, ExcludeUnlink_NoRandomSave) { - const DisableSave ds; - // TODO(gvisor.dev/issue/1624): This test fails on VFS1. - SKIP_IF(IsRunningWithVFS1()); - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), file.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - - // Unlink the child, which should cause further operations on the open file - // descriptor to be ignored. - ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds()); - int val = 0; - ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, AreUnordered({ - Event(IN_ATTRIB, file_wd), - Event(IN_DELETE, dir_wd, Basename(file.path())), - })); - - fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - ASSERT_THAT(events, Are({ - Event(IN_DELETE_SELF, file_wd), - Event(IN_IGNORED, file_wd), - })); -} - -// We need to disable S/R because there are filesystems where we cannot re-open -// fds to an unlinked file across S/R, e.g. gofer-backed filesytems. -TEST(Inotify, ExcludeUnlinkDirectory_NoRandomSave) { - // TODO(gvisor.dev/issue/1624): This test fails on VFS1. Remove once VFS1 is - // deleted. - SKIP_IF(IsRunningWithVFS1()); - - const DisableSave ds; - - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath dir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); - std::string dirPath = dir.path(); - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dirPath.c_str(), O_RDONLY | O_DIRECTORY)); - const int parent_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), parent.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - const int self_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - - // Unlink the dir, and then close the open fd. - ASSERT_THAT(rmdir(dirPath.c_str()), SyscallSucceeds()); - dir.reset(); - - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - // No close event should appear. - ASSERT_THAT(events, - Are({Event(IN_DELETE | IN_ISDIR, parent_wd, Basename(dirPath))})); - - fd.reset(); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - ASSERT_THAT(events, Are({ - Event(IN_DELETE_SELF, self_wd), - Event(IN_IGNORED, self_wd), - })); -} - -// If "dir/child" and "dir/child2" are links to the same file, and "dir/child" -// is unlinked, a watch on "dir" with IN_EXCL_UNLINK will exclude future events -// for fds on "dir/child" but not "dir/child2". -// -// We need to disable S/R because there are filesystems where we cannot re-open -// fds to an unlinked file across S/R, e.g. gofer-backed filesytems. -TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) { - // Inotify does not work properly with hard links in gofer and overlay fs. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); - // TODO(gvisor.dev/issue/1624): This test fails on VFS1. - SKIP_IF(IsRunningWithVFS1()); - - const DisableSave ds; - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - std::string path1 = file.path(); - std::string path2 = NewTempAbsPathInDir(dir.path()); - ASSERT_THAT(link(path1.c_str(), path2.c_str()), SyscallSucceeds()); - - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR)); - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open(path2.c_str(), O_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - - // After unlinking path1, only events on the fd for path2 should be generated. - ASSERT_THAT(unlink(path1.c_str()), SyscallSucceeds()); - ASSERT_THAT(write(fd1.get(), "x", 1), SyscallSucceeds()); - ASSERT_THAT(write(fd2.get(), "x", 1), SyscallSucceeds()); - - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_DELETE, wd, Basename(path1)), - Event(IN_MODIFY, wd, Basename(path2)), - })); -} - -// On native Linux, actions of data type FSNOTIFY_EVENT_INODE are not affected -// by IN_EXCL_UNLINK (see -// fs/notify/inotify/inotify_fsnotify.c:inotify_handle_event). Inode-level -// events include changes to metadata and extended attributes. -// -// We need to disable S/R because there are filesystems where we cannot re-open -// fds to an unlinked file across S/R, e.g. gofer-backed filesytems. -TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) { - // TODO(gvisor.dev/issue/1624): Fails on VFS1. - SKIP_IF(IsRunningWithVFS1()); - - // NOTE(gvisor.dev/issue/3654): In the gofer filesystem, we do not allow - // setting attributes through an fd if the file at the open path has been - // deleted. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(GetAbsoluteTestTmpdir()))); - - const DisableSave ds; - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); - - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch( - inotify_fd.get(), file.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK)); - - // Even after unlinking, inode-level operations will trigger events regardless - // of IN_EXCL_UNLINK. - ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds()); - - // Perform various actions on fd. - ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds()); - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, AnyOf(Are({ - Event(IN_ATTRIB, file_wd), - Event(IN_DELETE, dir_wd, Basename(file.path())), - Event(IN_MODIFY, dir_wd, Basename(file.path())), - Event(IN_MODIFY, file_wd), - }), - Are({ - Event(IN_DELETE, dir_wd, Basename(file.path())), - Event(IN_ATTRIB, file_wd), - Event(IN_MODIFY, dir_wd, Basename(file.path())), - Event(IN_MODIFY, file_wd), - }))); - - const struct timeval times[2] = {{1, 0}, {2, 0}}; - ASSERT_THAT(futimes(fd.get(), times), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_ATTRIB, dir_wd, Basename(file.path())), - Event(IN_ATTRIB, file_wd), - })); - - // S/R is disabled on this entire test due to behavior with unlink; it must - // also be disabled after this point because of fchmod. - ASSERT_THAT(fchmod(fd.get(), 0777), SyscallSucceeds()); - events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_ATTRIB, dir_wd, Basename(file.path())), - Event(IN_ATTRIB, file_wd), - })); -} - -TEST(Inotify, OneShot) { - // TODO(gvisor.dev/issue/1624): IN_ONESHOT not supported in VFS1. - SKIP_IF(IsRunningWithVFS1()); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor inotify_fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - - const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(inotify_fd.get(), file.path(), IN_MODIFY | IN_ONESHOT)); - - // Open an fd, write to it, and then close it. - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - ASSERT_THAT(write(fd.get(), "x", 1), SyscallSucceedsWithValue(1)); - fd.reset(); - - // We should get a single event followed by IN_IGNORED indicating removal - // of the one-shot watch. Prior activity (i.e. open) that is not in the mask - // should not trigger removal, and activity after removal (i.e. close) should - // not generate events. - std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - EXPECT_THAT(events, Are({ - Event(IN_MODIFY, wd), - Event(IN_IGNORED, wd), - })); - - // The watch should already have been removed. - EXPECT_THAT(inotify_rm_watch(inotify_fd.get(), wd), - SyscallFailsWithErrno(EINVAL)); -} - -// This test helps verify that the lock order of filesystem and inotify locks -// is respected when inotify instances and watch targets are concurrently being -// destroyed. -TEST(InotifyTest, InotifyAndTargetDestructionDoNotDeadlock_NoRandomSave) { - const DisableSave ds; // Too many syscalls. - - // A file descriptor protected by a mutex. This ensures that while a - // descriptor is in use, it cannot be closed and reused for a different file - // description. - struct atomic_fd { - int fd; - absl::Mutex mu; - }; - - // Set up initial inotify instances. - constexpr int num_fds = 3; - std::vector<atomic_fd> fds(num_fds); - for (int i = 0; i < num_fds; i++) { - int fd; - ASSERT_THAT(fd = inotify_init1(IN_NONBLOCK), SyscallSucceeds()); - fds[i].fd = fd; - } - - // Set up initial watch targets. - std::vector<std::string> paths; - for (int i = 0; i < 3; i++) { - paths.push_back(NewTempAbsPath()); - ASSERT_THAT(mknod(paths[i].c_str(), S_IFREG | 0600, 0), SyscallSucceeds()); - } - - constexpr absl::Duration runtime = absl::Seconds(4); - - // Constantly replace each inotify instance with a new one. - auto replace_fds = [&] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - for (auto& afd : fds) { - int new_fd; - ASSERT_THAT(new_fd = inotify_init1(IN_NONBLOCK), SyscallSucceeds()); - absl::MutexLock l(&afd.mu); - ASSERT_THAT(close(afd.fd), SyscallSucceeds()); - afd.fd = new_fd; - for (auto& p : paths) { - // inotify_add_watch may fail if the file at p was deleted. - ASSERT_THAT(inotify_add_watch(afd.fd, p.c_str(), IN_ALL_EVENTS), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - } - } - sched_yield(); - } - }; - - std::list<ScopedThread> ts; - for (int i = 0; i < 3; i++) { - ts.emplace_back(replace_fds); - } - - // Constantly replace each watch target with a new one. - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - for (auto& p : paths) { - ASSERT_THAT(unlink(p.c_str()), SyscallSucceeds()); - ASSERT_THAT(mknod(p.c_str(), S_IFREG | 0600, 0), SyscallSucceeds()); - } - sched_yield(); - } -} - -// This test helps verify that the lock order of filesystem and inotify locks -// is respected when adding/removing watches occurs concurrently with the -// removal of their targets. -TEST(InotifyTest, AddRemoveUnlinkDoNotDeadlock_NoRandomSave) { - const DisableSave ds; // Too many syscalls. - - // Set up inotify instances. - constexpr int num_fds = 3; - std::vector<int> fds(num_fds); - for (int i = 0; i < num_fds; i++) { - ASSERT_THAT(fds[i] = inotify_init1(IN_NONBLOCK), SyscallSucceeds()); - } - - // Set up initial watch targets. - std::vector<std::string> paths; - for (int i = 0; i < 3; i++) { - paths.push_back(NewTempAbsPath()); - ASSERT_THAT(mknod(paths[i].c_str(), S_IFREG | 0600, 0), SyscallSucceeds()); - } - - constexpr absl::Duration runtime = absl::Seconds(1); - - // Constantly add/remove watches for each inotify instance/watch target pair. - auto add_remove_watches = [&] { - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - for (int fd : fds) { - for (auto& p : paths) { - // Do not assert on inotify_add_watch and inotify_rm_watch. They may - // fail if the file at p was deleted. inotify_add_watch may also fail - // if another thread beat us to adding a watch. - const int wd = inotify_add_watch(fd, p.c_str(), IN_ALL_EVENTS); - if (wd > 0) { - inotify_rm_watch(fd, wd); - } - } - } - sched_yield(); - } - }; - - std::list<ScopedThread> ts; - for (int i = 0; i < 15; i++) { - ts.emplace_back(add_remove_watches); - } - - // Constantly replace each watch target with a new one. - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - for (auto& p : paths) { - ASSERT_THAT(unlink(p.c_str()), SyscallSucceeds()); - ASSERT_THAT(mknod(p.c_str(), S_IFREG | 0600, 0), SyscallSucceeds()); - } - sched_yield(); - } -} - -// This test helps verify that the lock order of filesystem and inotify locks -// is respected when many inotify events and filesystem operations occur -// simultaneously. -TEST(InotifyTest, NotifyNoDeadlock_NoRandomSave) { - const DisableSave ds; // Too many syscalls. - - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string dir = parent.path(); - - // mu protects file, which will change on rename. - absl::Mutex mu; - std::string file = NewTempAbsPathInDir(dir); - ASSERT_THAT(mknod(file.c_str(), 0644 | S_IFREG, 0), SyscallSucceeds()); - - const absl::Duration runtime = absl::Milliseconds(300); - - // Add/remove watches on dir and file. - ScopedThread add_remove_watches([&] { - const FileDescriptor ifd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - int dir_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(ifd.get(), dir, IN_ALL_EVENTS)); - int file_wd; - { - absl::ReaderMutexLock l(&mu); - file_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(ifd.get(), file, IN_ALL_EVENTS)); - } - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - ASSERT_THAT(inotify_rm_watch(ifd.get(), file_wd), SyscallSucceeds()); - ASSERT_THAT(inotify_rm_watch(ifd.get(), dir_wd), SyscallSucceeds()); - dir_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(ifd.get(), dir, IN_ALL_EVENTS)); - { - absl::ReaderMutexLock l(&mu); - file_wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(ifd.get(), file, IN_ALL_EVENTS)); - } - sched_yield(); - } - }); - - // Modify attributes on dir and file. - ScopedThread stats([&] { - int fd, dir_fd; - { - absl::ReaderMutexLock l(&mu); - ASSERT_THAT(fd = open(file.c_str(), O_RDONLY), SyscallSucceeds()); - } - ASSERT_THAT(dir_fd = open(dir.c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - const struct timeval times[2] = {{1, 0}, {2, 0}}; - - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - { - absl::ReaderMutexLock l(&mu); - EXPECT_THAT(utimes(file.c_str(), times), SyscallSucceeds()); - } - EXPECT_THAT(futimes(fd, times), SyscallSucceeds()); - EXPECT_THAT(utimes(dir.c_str(), times), SyscallSucceeds()); - EXPECT_THAT(futimes(dir_fd, times), SyscallSucceeds()); - sched_yield(); - } - }); - - // Modify extended attributes on dir and file. - ScopedThread xattrs([&] { - // TODO(gvisor.dev/issue/1636): Support extended attributes in runsc gofer. - if (!IsRunningOnGvisor()) { - int fd; - { - absl::ReaderMutexLock l(&mu); - ASSERT_THAT(fd = open(file.c_str(), O_RDONLY), SyscallSucceeds()); - } - - const char* name = "user.test"; - int val = 123; - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - { - absl::ReaderMutexLock l(&mu); - ASSERT_THAT( - setxattr(file.c_str(), name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - ASSERT_THAT(removexattr(file.c_str(), name), SyscallSucceeds()); - } - - ASSERT_THAT(fsetxattr(fd, name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - ASSERT_THAT(fremovexattr(fd, name), SyscallSucceeds()); - sched_yield(); - } - } - }); - - // Read and write file's contents. Read and write dir's entries. - ScopedThread read_write([&] { - int fd; - { - absl::ReaderMutexLock l(&mu); - ASSERT_THAT(fd = open(file.c_str(), O_RDWR), SyscallSucceeds()); - } - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - int val = 123; - ASSERT_THAT(write(fd, &val, sizeof(val)), SyscallSucceeds()); - ASSERT_THAT(read(fd, &val, sizeof(val)), SyscallSucceeds()); - TempPath new_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir)); - ASSERT_NO_ERRNO(ListDir(dir, false)); - new_file.reset(); - sched_yield(); - } - }); - - // Rename file. - for (auto start = absl::Now(); absl::Now() - start < runtime;) { - const std::string new_path = NewTempAbsPathInDir(dir); - { - absl::WriterMutexLock l(&mu); - ASSERT_THAT(rename(file.c_str(), new_path.c_str()), SyscallSucceeds()); - file = new_path; - } - sched_yield(); - } -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ioctl.cc b/test/syscalls/linux/ioctl.cc deleted file mode 100644 index 9b16d1558..000000000 --- a/test/syscalls/linux/ioctl.cc +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <errno.h> -#include <fcntl.h> -#include <net/if.h> -#include <netdb.h> -#include <signal.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -bool CheckNonBlocking(int fd) { - int ret = fcntl(fd, F_GETFL, 0); - TEST_CHECK(ret != -1); - return (ret & O_NONBLOCK) == O_NONBLOCK; -} - -bool CheckCloExec(int fd) { - int ret = fcntl(fd, F_GETFD, 0); - TEST_CHECK(ret != -1); - return (ret & FD_CLOEXEC) == FD_CLOEXEC; -} - -class IoctlTest : public ::testing::Test { - protected: - void SetUp() override { - ASSERT_THAT(fd_ = open("/dev/null", O_RDONLY), SyscallSucceeds()); - } - - void TearDown() override { - if (fd_ >= 0) { - ASSERT_THAT(close(fd_), SyscallSucceeds()); - fd_ = -1; - } - } - - int fd() const { return fd_; } - - private: - int fd_ = -1; -}; - -TEST_F(IoctlTest, BadFileDescriptor) { - EXPECT_THAT(ioctl(-1 /* fd */, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(IoctlTest, InvalidControlNumber) { - EXPECT_THAT(ioctl(STDOUT_FILENO, 0), SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(IoctlTest, IoctlWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_PATH)); - - int set = 1; - EXPECT_THAT(ioctl(fd.get(), FIONBIO, &set), SyscallFailsWithErrno(EBADF)); - - EXPECT_THAT(ioctl(fd.get(), FIONCLEX), SyscallFailsWithErrno(EBADF)); - - EXPECT_THAT(ioctl(fd.get(), FIOCLEX), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(IoctlTest, FIONBIOSucceeds) { - EXPECT_FALSE(CheckNonBlocking(fd())); - int set = 1; - EXPECT_THAT(ioctl(fd(), FIONBIO, &set), SyscallSucceeds()); - EXPECT_TRUE(CheckNonBlocking(fd())); - set = 0; - EXPECT_THAT(ioctl(fd(), FIONBIO, &set), SyscallSucceeds()); - EXPECT_FALSE(CheckNonBlocking(fd())); -} - -TEST_F(IoctlTest, FIONBIOFails) { - EXPECT_THAT(ioctl(fd(), FIONBIO, nullptr), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(IoctlTest, FIONCLEXSucceeds) { - EXPECT_THAT(ioctl(fd(), FIONCLEX), SyscallSucceeds()); - EXPECT_FALSE(CheckCloExec(fd())); -} - -TEST_F(IoctlTest, FIOCLEXSucceeds) { - EXPECT_THAT(ioctl(fd(), FIOCLEX), SyscallSucceeds()); - EXPECT_TRUE(CheckCloExec(fd())); -} - -TEST_F(IoctlTest, FIOASYNCFails) { - EXPECT_THAT(ioctl(fd(), FIOASYNC, nullptr), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(IoctlTest, FIOASYNCSucceeds) { - // Not all FDs support FIOASYNC. - const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int before = -1; - ASSERT_THAT(before = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - - int set = 1; - EXPECT_THAT(ioctl(s.get(), FIOASYNC, &set), SyscallSucceeds()); - - int after_set = -1; - ASSERT_THAT(after_set = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - EXPECT_EQ(after_set, before | O_ASYNC) << "before was " << before; - - set = 0; - EXPECT_THAT(ioctl(s.get(), FIOASYNC, &set), SyscallSucceeds()); - - ASSERT_THAT(fcntl(s.get(), F_GETFL), SyscallSucceedsWithValue(before)); -} - -/* Count of the number of SIGIOs handled. */ -static volatile int io_received = 0; - -void inc_io_handler(int sig, siginfo_t* siginfo, void* arg) { io_received++; } - -TEST_F(IoctlTest, FIOASYNCNoTarget) { - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - // Count SIGIOs received. - io_received = 0; - struct sigaction sa; - sa.sa_sigaction = inc_io_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - // Actually allow SIGIO delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO)); - - int set = 1; - EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - - constexpr char kData[] = "abc"; - ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)), - SyscallSucceedsWithValue(sizeof(kData))); - - EXPECT_EQ(io_received, 0); -} - -TEST_F(IoctlTest, FIOASYNCSelfTarget) { - // FIXME(b/120624367): gVisor erroneously sends SIGIO on close(2), which would - // kill the test when pair goes out of scope. Temporarily ignore SIGIO so that - // that the close signal is ignored. - struct sigaction sa; - sa.sa_handler = SIG_IGN; - auto early_sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - // Count SIGIOs received. - io_received = 0; - sa.sa_sigaction = inc_io_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - // Actually allow SIGIO delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO)); - - int set = 1; - EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - - pid_t pid = getpid(); - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds()); - - constexpr char kData[] = "abc"; - ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)), - SyscallSucceedsWithValue(sizeof(kData))); - - EXPECT_EQ(io_received, 1); -} - -// Equivalent to FIOASYNCSelfTarget except that FIOSETOWN is called before -// FIOASYNC. -TEST_F(IoctlTest, FIOASYNCSelfTarget2) { - // FIXME(b/120624367): gVisor erroneously sends SIGIO on close(2), which would - // kill the test when pair goes out of scope. Temporarily ignore SIGIO so that - // that the close signal is ignored. - struct sigaction sa; - sa.sa_handler = SIG_IGN; - auto early_sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - // Count SIGIOs received. - io_received = 0; - sa.sa_sigaction = inc_io_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - // Actually allow SIGIO delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO)); - - pid_t pid = -1; - EXPECT_THAT(pid = getpid(), SyscallSucceeds()); - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds()); - - int set = 1; - EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - - constexpr char kData[] = "abc"; - ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)), - SyscallSucceedsWithValue(sizeof(kData))); - - EXPECT_EQ(io_received, 1); -} - -// Check that closing an FD does not result in an event. -TEST_F(IoctlTest, FIOASYNCSelfTargetClose) { - // Count SIGIOs received. - struct sigaction sa; - io_received = 0; - sa.sa_sigaction = inc_io_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - // Actually allow SIGIO delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO)); - - for (int i = 0; i < 2; i++) { - auto pair = ASSERT_NO_ERRNO_AND_VALUE( - UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - pid_t pid = getpid(); - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds()); - - int set = 1; - EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - } - - // FIXME(b/120624367): gVisor erroneously sends SIGIO on close. - SKIP_IF(IsRunningOnGvisor()); - - EXPECT_EQ(io_received, 0); -} - -TEST_F(IoctlTest, FIOASYNCInvalidPID) { - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - int set = 1; - ASSERT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - pid_t pid = INT_MAX; - // This succeeds (with behavior equivalent to a pid of 0) in Linux prior to - // f73127356f34 "fs/fcntl: return -ESRCH in f_setown when pid/pgid can't be - // found", and fails with EPERM after that commit. - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ESRCH))); -} - -TEST_F(IoctlTest, FIOASYNCUnsetTarget) { - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - // Count SIGIOs received. - io_received = 0; - struct sigaction sa; - sa.sa_sigaction = inc_io_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - auto sa_cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGIO, sa)); - - // Actually allow SIGIO delivery. - auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGIO)); - - int set = 1; - EXPECT_THAT(ioctl(pair->second_fd(), FIOASYNC, &set), SyscallSucceeds()); - - pid_t pid = getpid(); - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds()); - - // Passing a PID of 0 unsets the target. - pid = 0; - EXPECT_THAT(ioctl(pair->second_fd(), FIOSETOWN, &pid), SyscallSucceeds()); - - constexpr char kData[] = "abc"; - ASSERT_THAT(WriteFd(pair->first_fd(), kData, sizeof(kData)), - SyscallSucceedsWithValue(sizeof(kData))); - - EXPECT_EQ(io_received, 0); -} - -using IoctlTestSIOCGIFCONF = SimpleSocketTest; - -TEST_P(IoctlTestSIOCGIFCONF, ValidateNoArrayGetsLength) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Validate that no array can be used to get the length required. - struct ifconf ifconf = {}; - ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds()); - ASSERT_GT(ifconf.ifc_len, 0); -} - -// This test validates that we will only return a partial array list and not -// partial ifrreq structs. -TEST_P(IoctlTestSIOCGIFCONF, ValidateNoPartialIfrsReturned) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - struct ifreq ifr = {}; - struct ifconf ifconf = {}; - ifconf.ifc_len = sizeof(ifr) - 1; // One byte too few. - ifconf.ifc_ifcu.ifcu_req = 𝔦 - - ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds()); - ASSERT_EQ(ifconf.ifc_len, 0); - ASSERT_EQ(ifr.ifr_name[0], '\0'); // Nothing is returned. - - ifconf.ifc_len = sizeof(ifreq); - ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds()); - ASSERT_GT(ifconf.ifc_len, 0); - ASSERT_NE(ifr.ifr_name[0], '\0'); // An interface can now be returned. -} - -TEST_P(IoctlTestSIOCGIFCONF, ValidateLoopbackIsPresent) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - struct ifconf ifconf = {}; - struct ifreq ifr[10] = {}; // Storage for up to 10 interfaces. - - ifconf.ifc_req = ifr; - ifconf.ifc_len = sizeof(ifr); - - ASSERT_THAT(ioctl(fd->get(), SIOCGIFCONF, &ifconf), SyscallSucceeds()); - size_t num_if = ifconf.ifc_len / sizeof(struct ifreq); - - // We should have at least one interface. - ASSERT_GE(num_if, 1); - - // One of the interfaces should be a loopback. - bool found_loopback = false; - for (size_t i = 0; i < num_if; ++i) { - if (strcmp(ifr[i].ifr_name, "lo") == 0) { - // SIOCGIFCONF returns the ipv4 address of the interface, let's check it. - ASSERT_EQ(ifr[i].ifr_addr.sa_family, AF_INET); - - // Validate the address is correct for loopback. - sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ifr[i].ifr_addr); - ASSERT_EQ(htonl(sin->sin_addr.s_addr), INADDR_LOOPBACK); - - found_loopback = true; - break; - } - } - ASSERT_TRUE(found_loopback); -} - -std::vector<SocketKind> IoctlSocketTypes() { - return {SimpleSocket(AF_UNIX, SOCK_STREAM, 0), - SimpleSocket(AF_UNIX, SOCK_DGRAM, 0), - SimpleSocket(AF_INET, SOCK_STREAM, 0), - SimpleSocket(AF_INET6, SOCK_STREAM, 0), - SimpleSocket(AF_INET, SOCK_DGRAM, 0), - SimpleSocket(AF_INET6, SOCK_DGRAM, 0)}; -} - -INSTANTIATE_TEST_SUITE_P(IoctlTest, IoctlTestSIOCGIFCONF, - ::testing::ValuesIn(IoctlSocketTypes())); - -} // namespace - -TEST_F(IoctlTest, FIOGETOWNSucceeds) { - const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int get = -1; - ASSERT_THAT(ioctl(s.get(), FIOGETOWN, &get), SyscallSucceeds()); - EXPECT_EQ(get, 0); -} - -TEST_F(IoctlTest, SIOCGPGRPSucceeds) { - const FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); - - int get = -1; - ASSERT_THAT(ioctl(s.get(), SIOCGPGRP, &get), SyscallSucceeds()); - EXPECT_EQ(get, 0); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ip6tables.cc b/test/syscalls/linux/ip6tables.cc deleted file mode 100644 index e0e146067..000000000 --- a/test/syscalls/linux/ip6tables.cc +++ /dev/null @@ -1,233 +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. - -#include <linux/capability.h> -#include <sys/socket.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/iptables.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr char kNatTablename[] = "nat"; -constexpr char kErrorTarget[] = "ERROR"; -constexpr size_t kEmptyStandardEntrySize = - sizeof(struct ip6t_entry) + sizeof(struct xt_standard_target); -constexpr size_t kEmptyErrorEntrySize = - sizeof(struct ip6t_entry) + sizeof(struct xt_error_target); - -TEST(IP6TablesBasic, FailSockoptNonRaw) { - // Even if the user has CAP_NET_RAW, they shouldn't be able to use the - // ip6tables sockopts with a non-raw socket. - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds()); - - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info); - EXPECT_THAT(getsockopt(sock, SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size), - SyscallFailsWithErrno(ENOPROTOOPT)); - - EXPECT_THAT(close(sock), SyscallSucceeds()); -} - -TEST(IP6TablesBasic, GetInfoErrorPrecedence) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds()); - - // When using the wrong type of socket and a too-short optlen, we should get - // EINVAL. - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info) - 1; - EXPECT_THAT(getsockopt(sock, SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(IP6TablesBasic, GetEntriesErrorPrecedence) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET6, SOCK_DGRAM, 0), SyscallSucceeds()); - - // When using the wrong type of socket and a too-short optlen, we should get - // EINVAL. - struct ip6t_get_entries entries = {}; - socklen_t entries_size = sizeof(struct ip6t_get_entries) - 1; - snprintf(entries.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - EXPECT_THAT( - getsockopt(sock, SOL_IPV6, IP6T_SO_GET_ENTRIES, &entries, &entries_size), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(IP6TablesBasic, GetRevision) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), - SyscallSucceeds()); - - struct xt_get_revision rev = {}; - socklen_t rev_len = sizeof(rev); - - snprintf(rev.name, sizeof(rev.name), "REDIRECT"); - rev.revision = 0; - - // Revision 0 exists. - EXPECT_THAT( - getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len), - SyscallSucceeds()); - EXPECT_EQ(rev.revision, 0); - - // Revisions > 0 don't exist. - rev.revision = 1; - EXPECT_THAT( - getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len), - SyscallFailsWithErrno(EPROTONOSUPPORT)); -} - -// This tests the initial state of a machine with empty ip6tables via -// getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are -// empty when running in native, but we can test that gVisor has the same -// initial state that a newly-booted Linux machine would have. -TEST(IP6TablesTest, InitialInfo) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)); - - // Get info via sockopt. - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info); - ASSERT_THAT( - getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size), - SyscallSucceeds()); - - // The nat table supports PREROUTING, and OUTPUT. - unsigned int valid_hooks = - (1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_OUT) | - (1 << NF_IP6_POST_ROUTING) | (1 << NF_IP6_LOCAL_IN); - EXPECT_EQ(info.valid_hooks, valid_hooks); - - // Each chain consists of an empty entry with a standard target.. - EXPECT_EQ(info.hook_entry[NF_IP6_PRE_ROUTING], 0); - EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize); - EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2); - EXPECT_EQ(info.hook_entry[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3); - - // The underflow points are the same as the entry points. - EXPECT_EQ(info.underflow[NF_IP6_PRE_ROUTING], 0); - EXPECT_EQ(info.underflow[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize); - EXPECT_EQ(info.underflow[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2); - EXPECT_EQ(info.underflow[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3); - - // One entry for each chain, plus an error entry at the end. - EXPECT_EQ(info.num_entries, 5); - - EXPECT_EQ(info.size, 4 * kEmptyStandardEntrySize + kEmptyErrorEntrySize); - EXPECT_EQ(strcmp(info.name, kNatTablename), 0); -} - -// This tests the initial state of a machine with empty ip6tables via -// getsockopt(IP6T_SO_GET_ENTRIES). We don't have a guarantee that the iptables -// are empty when running in native, but we can test that gVisor has the same -// initial state that a newly-booted Linux machine would have. -TEST(IP6TablesTest, InitialEntries) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)); - - // Get info via sockopt. - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info); - ASSERT_THAT( - getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size), - SyscallSucceeds()); - - // Use info to get entries. - socklen_t entries_size = sizeof(struct ip6t_get_entries) + info.size; - struct ip6t_get_entries* entries = - static_cast<struct ip6t_get_entries*>(malloc(entries_size)); - snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - entries->size = info.size; - ASSERT_THAT(getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_ENTRIES, entries, - &entries_size), - SyscallSucceeds()); - - // Verify the name and size. - ASSERT_EQ(info.size, entries->size); - ASSERT_EQ(strcmp(entries->name, kNatTablename), 0); - - // Verify that the entrytable is 4 entries with accept targets and no matches - // followed by a single error target. - size_t entry_offset = 0; - while (entry_offset < entries->size) { - struct ip6t_entry* entry = reinterpret_cast<struct ip6t_entry*>( - reinterpret_cast<char*>(entries->entrytable) + entry_offset); - - // ipv6 should be zeroed. - struct ip6t_ip6 zeroed = {}; - ASSERT_EQ(memcmp(static_cast<void*>(&zeroed), - static_cast<void*>(&entry->ipv6), sizeof(zeroed)), - 0); - - // target_offset should be zero. - EXPECT_EQ(entry->target_offset, sizeof(ip6t_entry)); - - if (entry_offset < kEmptyStandardEntrySize * 4) { - // The first 4 entries are standard targets - struct xt_standard_target* target = - reinterpret_cast<struct xt_standard_target*>(entry->elems); - EXPECT_EQ(entry->next_offset, kEmptyStandardEntrySize); - EXPECT_EQ(target->target.u.user.target_size, sizeof(*target)); - EXPECT_EQ(strcmp(target->target.u.user.name, ""), 0); - EXPECT_EQ(target->target.u.user.revision, 0); - // This is what's returned for an accept verdict. I don't know why. - EXPECT_EQ(target->verdict, -NF_ACCEPT - 1); - } else { - // The last entry is an error target - struct xt_error_target* target = - reinterpret_cast<struct xt_error_target*>(entry->elems); - EXPECT_EQ(entry->next_offset, kEmptyErrorEntrySize); - EXPECT_EQ(target->target.u.user.target_size, sizeof(*target)); - EXPECT_EQ(strcmp(target->target.u.user.name, kErrorTarget), 0); - EXPECT_EQ(target->target.u.user.revision, 0); - EXPECT_EQ(strcmp(target->errorname, kErrorTarget), 0); - } - - entry_offset += entry->next_offset; - break; - } - - free(entries); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc deleted file mode 100644 index 98d07ae85..000000000 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/ip_socket_test_util.h" - -#include <net/if.h> -#include <netinet/in.h> -#include <sys/socket.h> - -#include <cstring> - -namespace gvisor { -namespace testing { - -uint32_t IPFromInetSockaddr(const struct sockaddr* addr) { - auto* in_addr = reinterpret_cast<const struct sockaddr_in*>(addr); - return in_addr->sin_addr.s_addr; -} - -uint16_t PortFromInetSockaddr(const struct sockaddr* addr) { - auto* in_addr = reinterpret_cast<const struct sockaddr_in*>(addr); - return ntohs(in_addr->sin_port); -} - -PosixErrorOr<int> InterfaceIndex(std::string name) { - int index = if_nametoindex(name.c_str()); - if (index) { - return index; - } - return PosixError(errno); -} - -namespace { - -std::string DescribeSocketType(int type) { - return absl::StrCat(((type & SOCK_NONBLOCK) != 0) ? "non-blocking " : "", - ((type & SOCK_CLOEXEC) != 0) ? "close-on-exec " : ""); -} - -} // namespace - -SocketPairKind IPv6TCPAcceptBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv6 TCP socket"); - return SocketPairKind{ - description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind IPv4TCPAcceptBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv4 TCP socket"); - return SocketPairKind{ - description, AF_INET, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindSocketPairCreator(AF_INET, type | SOCK_STREAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind DualStackTCPAcceptBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected dual stack TCP socket"); - return SocketPairKind{ - description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0, - /* dual_stack = */ true)}; -} - -SocketPairKind IPv6TCPAcceptBindPersistentListenerSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv6 TCP socket"); - return SocketPairKind{description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindPersistentListenerSocketPairCreator( - AF_INET6, type | SOCK_STREAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind IPv4TCPAcceptBindPersistentListenerSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv4 TCP socket"); - return SocketPairKind{description, AF_INET, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindPersistentListenerSocketPairCreator( - AF_INET, type | SOCK_STREAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind DualStackTCPAcceptBindPersistentListenerSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected dual stack TCP socket"); - return SocketPairKind{description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP, - TCPAcceptBindPersistentListenerSocketPairCreator( - AF_INET6, type | SOCK_STREAM, 0, - /* dual_stack = */ true)}; -} - -SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv6 UDP socket"); - return SocketPairKind{ - description, AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP, - UDPBidirectionalBindSocketPairCreator(AF_INET6, type | SOCK_DGRAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected IPv4 UDP socket"); - return SocketPairKind{ - description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP, - UDPBidirectionalBindSocketPairCreator(AF_INET, type | SOCK_DGRAM, 0, - /* dual_stack = */ false)}; -} - -SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "connected dual stack UDP socket"); - return SocketPairKind{ - description, AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP, - UDPBidirectionalBindSocketPairCreator(AF_INET6, type | SOCK_DGRAM, 0, - /* dual_stack = */ true)}; -} - -SocketPairKind IPv4UDPUnboundSocketPair(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket"); - return SocketPairKind{ - description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP, - UDPUnboundSocketPairCreator(AF_INET, type | SOCK_DGRAM, 0, - /* dual_stack = */ false)}; -} - -SocketKind IPv4UDPUnboundSocket(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket"); - return SocketKind{ - description, AF_INET, type | SOCK_DGRAM, IPPROTO_UDP, - UnboundSocketCreator(AF_INET, type | SOCK_DGRAM, IPPROTO_UDP)}; -} - -SocketKind IPv6UDPUnboundSocket(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "IPv6 UDP socket"); - return SocketKind{ - description, AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP, - UnboundSocketCreator(AF_INET6, type | SOCK_DGRAM, IPPROTO_UDP)}; -} - -SocketKind IPv4TCPUnboundSocket(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "IPv4 TCP socket"); - return SocketKind{ - description, AF_INET, type | SOCK_STREAM, IPPROTO_TCP, - UnboundSocketCreator(AF_INET, type | SOCK_STREAM, IPPROTO_TCP)}; -} - -SocketKind IPv6TCPUnboundSocket(int type) { - std::string description = - absl::StrCat(DescribeSocketType(type), "IPv6 TCP socket"); - return SocketKind{ - description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP, - UnboundSocketCreator(AF_INET6, type | SOCK_STREAM, IPPROTO_TCP)}; -} - -PosixError IfAddrHelper::Load() { - Release(); - RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_)); - return NoError(); -} - -void IfAddrHelper::Release() { - if (ifaddr_) { - freeifaddrs(ifaddr_); - ifaddr_ = nullptr; - } -} - -std::vector<std::string> IfAddrHelper::InterfaceList(int family) const { - std::vector<std::string> names; - for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { - continue; - } - names.emplace(names.end(), ifa->ifa_name); - } - return names; -} - -const sockaddr* IfAddrHelper::GetAddr(int family, std::string name) const { - for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { - continue; - } - if (name == ifa->ifa_name) { - return ifa->ifa_addr; - } - } - return nullptr; -} - -PosixErrorOr<int> IfAddrHelper::GetIndex(std::string name) const { - return InterfaceIndex(name); -} - -std::string GetAddr4Str(const in_addr* a) { - char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, a, str, sizeof(str)); - return std::string(str); -} - -std::string GetAddr6Str(const in6_addr* a) { - char str[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, a, str, sizeof(str)); - return std::string(str); -} - -std::string GetAddrStr(const sockaddr* a) { - if (a->sa_family == AF_INET) { - auto src = &(reinterpret_cast<const sockaddr_in*>(a)->sin_addr); - return GetAddr4Str(src); - } else if (a->sa_family == AF_INET6) { - auto src = &(reinterpret_cast<const sockaddr_in6*>(a)->sin6_addr); - return GetAddr6Str(src); - } - return std::string("<invalid>"); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h deleted file mode 100644 index 9c3859fcd..000000000 --- a/test/syscalls/linux/ip_socket_test_util.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_ - -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <sys/types.h> - -#include <string> - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Extracts the IP address from an inet sockaddr in network byte order. -uint32_t IPFromInetSockaddr(const struct sockaddr* addr); - -// Extracts the port from an inet sockaddr in host byte order. -uint16_t PortFromInetSockaddr(const struct sockaddr* addr); - -// InterfaceIndex returns the index of the named interface. -PosixErrorOr<int> InterfaceIndex(std::string name); - -// IPv6TCPAcceptBindSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and accept() syscalls with AF_INET6 and the -// given type bound to the IPv6 loopback. -SocketPairKind IPv6TCPAcceptBindSocketPair(int type); - -// IPv4TCPAcceptBindSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and accept() syscalls with AF_INET and the -// given type bound to the IPv4 loopback. -SocketPairKind IPv4TCPAcceptBindSocketPair(int type); - -// DualStackTCPAcceptBindSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and accept() syscalls with AF_INET6 and the -// given type bound to the IPv4 loopback. -SocketPairKind DualStackTCPAcceptBindSocketPair(int type); - -// IPv6TCPAcceptBindPersistentListenerSocketPair is like -// IPv6TCPAcceptBindSocketPair except it uses a persistent listening socket to -// create all socket pairs. -SocketPairKind IPv6TCPAcceptBindPersistentListenerSocketPair(int type); - -// IPv4TCPAcceptBindPersistentListenerSocketPair is like -// IPv4TCPAcceptBindSocketPair except it uses a persistent listening socket to -// create all socket pairs. -SocketPairKind IPv4TCPAcceptBindPersistentListenerSocketPair(int type); - -// DualStackTCPAcceptBindPersistentListenerSocketPair is like -// DualStackTCPAcceptBindSocketPair except it uses a persistent listening socket -// to create all socket pairs. -SocketPairKind DualStackTCPAcceptBindPersistentListenerSocketPair(int type); - -// IPv6UDPBidirectionalBindSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and connect() syscalls with AF_INET6 and the -// given type bound to the IPv6 loopback. -SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type); - -// IPv4UDPBidirectionalBindSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and connect() syscalls with AF_INET and the -// given type bound to the IPv4 loopback. -SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type); - -// DualStackUDPBidirectionalBindSocketPair returns a SocketPairKind that -// represents SocketPairs created with bind() and connect() syscalls with -// AF_INET6 and the given type bound to the IPv4 loopback. -SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type); - -// IPv4UDPUnboundSocketPair returns a SocketPairKind that represents -// SocketPairs created with AF_INET and the given type. -SocketPairKind IPv4UDPUnboundSocketPair(int type); - -// IPv4UDPUnboundSocket returns a SocketKind that represents a SimpleSocket -// created with AF_INET, SOCK_DGRAM, and the given type. -SocketKind IPv4UDPUnboundSocket(int type); - -// IPv6UDPUnboundSocket returns a SocketKind that represents a SimpleSocket -// created with AF_INET6, SOCK_DGRAM, and the given type. -SocketKind IPv6UDPUnboundSocket(int type); - -// IPv4TCPUnboundSocket returns a SocketKind that represents a SimpleSocket -// created with AF_INET, SOCK_STREAM and the given type. -SocketKind IPv4TCPUnboundSocket(int type); - -// IPv6TCPUnboundSocket returns a SocketKind that represents a SimpleSocket -// created with AF_INET6, SOCK_STREAM and the given type. -SocketKind IPv6TCPUnboundSocket(int type); - -// IfAddrHelper is a helper class that determines the local interfaces present -// and provides functions to obtain their names, index numbers, and IP address. -class IfAddrHelper { - public: - IfAddrHelper() : ifaddr_(nullptr) {} - ~IfAddrHelper() { Release(); } - - PosixError Load(); - void Release(); - - std::vector<std::string> InterfaceList(int family) const; - - const sockaddr* GetAddr(int family, std::string name) const; - PosixErrorOr<int> GetIndex(std::string name) const; - - private: - struct ifaddrs* ifaddr_; -}; - -// GetAddr4Str returns the given IPv4 network address structure as a string. -std::string GetAddr4Str(const in_addr* a); - -// GetAddr6Str returns the given IPv6 network address structure as a string. -std::string GetAddr6Str(const in6_addr* a); - -// GetAddrStr returns the given IPv4 or IPv6 network address structure as a -// string. -std::string GetAddrStr(const sockaddr* a); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_ diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc deleted file mode 100644 index 22550b800..000000000 --- a/test/syscalls/linux/iptables.cc +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/iptables.h" - -#include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/netfilter/x_tables.h> -#include <net/if.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <stdio.h> -#include <sys/poll.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr char kNatTablename[] = "nat"; -constexpr char kErrorTarget[] = "ERROR"; -constexpr size_t kEmptyStandardEntrySize = - sizeof(struct ipt_entry) + sizeof(struct ipt_standard_target); -constexpr size_t kEmptyErrorEntrySize = - sizeof(struct ipt_entry) + sizeof(struct ipt_error_target); - -TEST(IPTablesBasic, CreateSocket) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), - SyscallSucceeds()); - - ASSERT_THAT(close(sock), SyscallSucceeds()); -} - -TEST(IPTablesBasic, FailSockoptNonRaw) { - // Even if the user has CAP_NET_RAW, they shouldn't be able to use the - // iptables sockopts with a non-raw socket. - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds()); - - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info); - EXPECT_THAT(getsockopt(sock, SOL_IP, IPT_SO_GET_INFO, &info, &info_size), - SyscallFailsWithErrno(ENOPROTOOPT)); - - ASSERT_THAT(close(sock), SyscallSucceeds()); -} - -TEST(IPTablesBasic, GetInfoErrorPrecedence) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds()); - - // When using the wrong type of socket and a too-short optlen, we should get - // EINVAL. - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info) - 1; - ASSERT_THAT(getsockopt(sock, SOL_IP, IPT_SO_GET_INFO, &info, &info_size), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(IPTablesBasic, GetEntriesErrorPrecedence) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds()); - - // When using the wrong type of socket and a too-short optlen, we should get - // EINVAL. - struct ipt_get_entries entries = {}; - socklen_t entries_size = sizeof(struct ipt_get_entries) - 1; - snprintf(entries.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - ASSERT_THAT( - getsockopt(sock, SOL_IP, IPT_SO_GET_ENTRIES, &entries, &entries_size), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(IPTablesBasic, OriginalDstErrors) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_STREAM, 0), SyscallSucceeds()); - - // Sockets not affected by NAT should fail to find an original destination. - struct sockaddr_in addr = {}; - socklen_t addr_len = sizeof(addr); - EXPECT_THAT(getsockopt(sock, SOL_IP, SO_ORIGINAL_DST, &addr, &addr_len), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST(IPTablesBasic, GetRevision) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), - SyscallSucceeds()); - - struct xt_get_revision rev = {}; - socklen_t rev_len = sizeof(rev); - - snprintf(rev.name, sizeof(rev.name), "REDIRECT"); - rev.revision = 0; - - // Revision 0 exists. - EXPECT_THAT( - getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len), - SyscallSucceeds()); - EXPECT_EQ(rev.revision, 0); - - // Revisions > 0 don't exist. - rev.revision = 1; - EXPECT_THAT( - getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len), - SyscallFailsWithErrno(EPROTONOSUPPORT)); -} - -// Fixture for iptables tests. -class IPTablesTest : public ::testing::Test { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // The socket via which to manipulate iptables. - int s_; -}; - -void IPTablesTest::SetUp() { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallSucceeds()); -} - -void IPTablesTest::TearDown() { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - EXPECT_THAT(close(s_), SyscallSucceeds()); -} - -// This tests the initial state of a machine with empty iptables. We don't have -// a guarantee that the iptables are empty when running in native, but we can -// test that gVisor has the same initial state that a newly-booted Linux machine -// would have. -TEST_F(IPTablesTest, InitialState) { - SKIP_IF(!IsRunningOnGvisor()); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // - // Get info via sockopt. - // - struct ipt_getinfo info = {}; - snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - socklen_t info_size = sizeof(info); - ASSERT_THAT(getsockopt(s_, SOL_IP, IPT_SO_GET_INFO, &info, &info_size), - SyscallSucceeds()); - - // The nat table supports PREROUTING, and OUTPUT. - unsigned int valid_hooks = (1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT) | - (1 << NF_IP_POST_ROUTING) | (1 << NF_IP_LOCAL_IN); - - EXPECT_EQ(info.valid_hooks, valid_hooks); - - // Each chain consists of an empty entry with a standard target.. - EXPECT_EQ(info.hook_entry[NF_IP_PRE_ROUTING], 0); - EXPECT_EQ(info.hook_entry[NF_IP_LOCAL_IN], kEmptyStandardEntrySize); - EXPECT_EQ(info.hook_entry[NF_IP_LOCAL_OUT], kEmptyStandardEntrySize * 2); - EXPECT_EQ(info.hook_entry[NF_IP_POST_ROUTING], kEmptyStandardEntrySize * 3); - - // The underflow points are the same as the entry points. - EXPECT_EQ(info.underflow[NF_IP_PRE_ROUTING], 0); - EXPECT_EQ(info.underflow[NF_IP_LOCAL_IN], kEmptyStandardEntrySize); - EXPECT_EQ(info.underflow[NF_IP_LOCAL_OUT], kEmptyStandardEntrySize * 2); - EXPECT_EQ(info.underflow[NF_IP_POST_ROUTING], kEmptyStandardEntrySize * 3); - - // One entry for each chain, plus an error entry at the end. - EXPECT_EQ(info.num_entries, 5); - - EXPECT_EQ(info.size, 4 * kEmptyStandardEntrySize + kEmptyErrorEntrySize); - EXPECT_EQ(strcmp(info.name, kNatTablename), 0); - - // - // Use info to get entries. - // - socklen_t entries_size = sizeof(struct ipt_get_entries) + info.size; - struct ipt_get_entries* entries = - static_cast<struct ipt_get_entries*>(malloc(entries_size)); - snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename); - entries->size = info.size; - ASSERT_THAT( - getsockopt(s_, SOL_IP, IPT_SO_GET_ENTRIES, entries, &entries_size), - SyscallSucceeds()); - - // Verify the name and size. - ASSERT_EQ(info.size, entries->size); - ASSERT_EQ(strcmp(entries->name, kNatTablename), 0); - - // Verify that the entrytable is 4 entries with accept targets and no matches - // followed by a single error target. - size_t entry_offset = 0; - while (entry_offset < entries->size) { - struct ipt_entry* entry = reinterpret_cast<struct ipt_entry*>( - reinterpret_cast<char*>(entries->entrytable) + entry_offset); - - // ip should be zeroes. - struct ipt_ip zeroed = {}; - EXPECT_EQ(memcmp(static_cast<void*>(&zeroed), - static_cast<void*>(&entry->ip), sizeof(zeroed)), - 0); - - // target_offset should be zero. - EXPECT_EQ(entry->target_offset, sizeof(ipt_entry)); - - if (entry_offset < kEmptyStandardEntrySize * 4) { - // The first 4 entries are standard targets - struct ipt_standard_target* target = - reinterpret_cast<struct ipt_standard_target*>(entry->elems); - EXPECT_EQ(entry->next_offset, kEmptyStandardEntrySize); - EXPECT_EQ(target->target.u.user.target_size, sizeof(*target)); - EXPECT_EQ(strcmp(target->target.u.user.name, ""), 0); - EXPECT_EQ(target->target.u.user.revision, 0); - // This is what's returned for an accept verdict. I don't know why. - EXPECT_EQ(target->verdict, -NF_ACCEPT - 1); - } else { - // The last entry is an error target - struct ipt_error_target* target = - reinterpret_cast<struct ipt_error_target*>(entry->elems); - EXPECT_EQ(entry->next_offset, kEmptyErrorEntrySize); - EXPECT_EQ(target->target.u.user.target_size, sizeof(*target)); - EXPECT_EQ(strcmp(target->target.u.user.name, kErrorTarget), 0); - EXPECT_EQ(target->target.u.user.revision, 0); - EXPECT_EQ(strcmp(target->errorname, kErrorTarget), 0); - } - - entry_offset += entry->next_offset; - } - - free(entries); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/iptables.h b/test/syscalls/linux/iptables.h deleted file mode 100644 index d0fc10fea..000000000 --- a/test/syscalls/linux/iptables.h +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// There are a number of structs and values that we can't #include because of a -// difference between C and C++ (C++ won't let you implicitly cast from void* to -// struct something*). We re-define them here. - -#ifndef GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_ -#define GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_ - -// Netfilter headers require some headers to preceed them. -// clang-format off -#include <netinet/in.h> -#include <stddef.h> -// clang-format on - -#include <linux/netfilter/x_tables.h> -#include <linux/netfilter_ipv4.h> -#include <linux/netfilter_ipv6.h> -#include <net/if.h> -#include <netinet/ip.h> -#include <stdint.h> - -// -// IPv4 ABI. -// - -#define ipt_standard_target xt_standard_target -#define ipt_entry_target xt_entry_target -#define ipt_error_target xt_error_target - -enum SockOpts { - // For setsockopt. - IPT_BASE_CTL = 64, - IPT_SO_SET_REPLACE = IPT_BASE_CTL, - IPT_SO_SET_ADD_COUNTERS = IPT_BASE_CTL + 1, - IPT_SO_SET_MAX = IPT_SO_SET_ADD_COUNTERS, - - // For getsockopt. - IPT_SO_GET_INFO = IPT_BASE_CTL, - IPT_SO_GET_ENTRIES = IPT_BASE_CTL + 1, - IPT_SO_GET_REVISION_MATCH = IPT_BASE_CTL + 2, - IPT_SO_GET_REVISION_TARGET = IPT_BASE_CTL + 3, - IPT_SO_GET_MAX = IPT_SO_GET_REVISION_TARGET -}; - -// ipt_ip specifies basic matching criteria that can be applied by examining -// only the IP header of a packet. -struct ipt_ip { - // Source IP address. - struct in_addr src; - - // Destination IP address. - struct in_addr dst; - - // Source IP address mask. - struct in_addr smsk; - - // Destination IP address mask. - struct in_addr dmsk; - - // Input interface. - char iniface[IFNAMSIZ]; - - // Output interface. - char outiface[IFNAMSIZ]; - - // Input interface mask. - unsigned char iniface_mask[IFNAMSIZ]; - - // Output interface mask. - unsigned char outiface_mask[IFNAMSIZ]; - - // Transport protocol. - uint16_t proto; - - // Flags. - uint8_t flags; - - // Inverse flags. - uint8_t invflags; -}; - -// ipt_entry is an iptables rule. It contains information about what packets the -// rule matches and what action (target) to perform for matching packets. -struct ipt_entry { - // Basic matching information used to match a packet's IP header. - struct ipt_ip ip; - - // A caching field that isn't used by userspace. - unsigned int nfcache; - - // The number of bytes between the start of this ipt_entry struct and the - // rule's target. - uint16_t target_offset; - - // The total size of this rule, from the beginning of the entry to the end of - // the target. - uint16_t next_offset; - - // A return pointer not used by userspace. - unsigned int comefrom; - - // Counters for packets and bytes, which we don't yet implement. - struct xt_counters counters; - - // The data for all this rules matches followed by the target. This runs - // beyond the value of sizeof(struct ipt_entry). - unsigned char elems[0]; -}; - -// Passed to getsockopt(IPT_SO_GET_INFO). -struct ipt_getinfo { - // The name of the table. The user only fills this in, the rest is filled in - // when returning from getsockopt. Currently "nat" and "mangle" are supported. - char name[XT_TABLE_MAXNAMELEN]; - - // A bitmap of which hooks apply to the table. For example, a table with hooks - // PREROUTING and FORWARD has the value - // (1 << NF_IP_PRE_REOUTING) | (1 << NF_IP_FORWARD). - unsigned int valid_hooks; - - // The offset into the entry table for each valid hook. The entry table is - // returned by getsockopt(IPT_SO_GET_ENTRIES). - unsigned int hook_entry[NF_IP_NUMHOOKS]; - - // For each valid hook, the underflow is the offset into the entry table to - // jump to in case traversing the table yields no verdict (although I have no - // clue how that could happen - builtin chains always end with a policy, and - // user-defined chains always end with a RETURN. - // - // The entry referred to must be an "unconditional" entry, meaning it has no - // matches, specifies no IP criteria, and either DROPs or ACCEPTs packets. It - // basically has to be capable of making a definitive decision no matter what - // it's passed. - unsigned int underflow[NF_IP_NUMHOOKS]; - - // The number of entries in the entry table returned by - // getsockopt(IPT_SO_GET_ENTRIES). - unsigned int num_entries; - - // The size of the entry table returned by getsockopt(IPT_SO_GET_ENTRIES). - unsigned int size; -}; - -// Passed to getsockopt(IPT_SO_GET_ENTRIES). -struct ipt_get_entries { - // The name of the table. The user fills this in. Currently "nat" and "mangle" - // are supported. - char name[XT_TABLE_MAXNAMELEN]; - - // The size of the entry table in bytes. The user fills this in with the value - // from struct ipt_getinfo.size. - unsigned int size; - - // The entries for the given table. This will run past the size defined by - // sizeof(struct ipt_get_entries). - struct ipt_entry entrytable[0]; -}; - -// Passed to setsockopt(SO_SET_REPLACE). -struct ipt_replace { - // The name of the table. - char name[XT_TABLE_MAXNAMELEN]; - - // The same as struct ipt_getinfo.valid_hooks. Users don't change this. - unsigned int valid_hooks; - - // The same as struct ipt_getinfo.num_entries. - unsigned int num_entries; - - // The same as struct ipt_getinfo.size. - unsigned int size; - - // The same as struct ipt_getinfo.hook_entry. - unsigned int hook_entry[NF_IP_NUMHOOKS]; - - // The same as struct ipt_getinfo.underflow. - unsigned int underflow[NF_IP_NUMHOOKS]; - - // The number of counters, which should equal the number of entries. - unsigned int num_counters; - - // The unchanged values from each ipt_entry's counters. - struct xt_counters* counters; - - // The entries to write to the table. This will run past the size defined by - // sizeof(srtuct ipt_replace); - struct ipt_entry entries[0]; -}; - -// -// IPv6 ABI. -// - -enum SockOpts6 { - // For setsockopt. - IP6T_BASE_CTL = 64, - IP6T_SO_SET_REPLACE = IP6T_BASE_CTL, - IP6T_SO_SET_ADD_COUNTERS = IP6T_BASE_CTL + 1, - IP6T_SO_SET_MAX = IP6T_SO_SET_ADD_COUNTERS, - - // For getsockopt. - IP6T_SO_GET_INFO = IP6T_BASE_CTL, - IP6T_SO_GET_ENTRIES = IP6T_BASE_CTL + 1, - IP6T_SO_GET_REVISION_MATCH = IP6T_BASE_CTL + 4, - IP6T_SO_GET_REVISION_TARGET = IP6T_BASE_CTL + 5, - IP6T_SO_GET_MAX = IP6T_SO_GET_REVISION_TARGET -}; - -// ip6t_ip6 specifies basic matching criteria that can be applied by examining -// only the IP header of a packet. -struct ip6t_ip6 { - // Source IP address. - struct in6_addr src; - - // Destination IP address. - struct in6_addr dst; - - // Source IP address mask. - struct in6_addr smsk; - - // Destination IP address mask. - struct in6_addr dmsk; - - // Input interface. - char iniface[IFNAMSIZ]; - - // Output interface. - char outiface[IFNAMSIZ]; - - // Input interface mask. - unsigned char iniface_mask[IFNAMSIZ]; - - // Output interface mask. - unsigned char outiface_mask[IFNAMSIZ]; - - // Transport protocol. - uint16_t proto; - - // TOS. - uint8_t tos; - - // Flags. - uint8_t flags; - - // Inverse flags. - uint8_t invflags; -}; - -// ip6t_entry is an ip6tables rule. -struct ip6t_entry { - // Basic matching information used to match a packet's IP header. - struct ip6t_ip6 ipv6; - - // A caching field that isn't used by userspace. - unsigned int nfcache; - - // The number of bytes between the start of this entry and the rule's target. - uint16_t target_offset; - - // The total size of this rule, from the beginning of the entry to the end of - // the target. - uint16_t next_offset; - - // A return pointer not used by userspace. - unsigned int comefrom; - - // Counters for packets and bytes, which we don't yet implement. - struct xt_counters counters; - - // The data for all this rules matches followed by the target. This runs - // beyond the value of sizeof(struct ip6t_entry). - unsigned char elems[0]; -}; - -// Passed to getsockopt(IP6T_SO_GET_ENTRIES). -struct ip6t_get_entries { - // The name of the table. - char name[XT_TABLE_MAXNAMELEN]; - - // The size of the entry table in bytes. The user fills this in with the value - // from struct ipt_getinfo.size. - unsigned int size; - - // The entries for the given table. This will run past the size defined by - // sizeof(struct ip6t_get_entries). - struct ip6t_entry entrytable[0]; -}; - -#endif // GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_ diff --git a/test/syscalls/linux/itimer.cc b/test/syscalls/linux/itimer.cc deleted file mode 100644 index e397d5f57..000000000 --- a/test/syscalls/linux/itimer.cc +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/types.h> -#include <time.h> - -#include <atomic> -#include <functional> -#include <iostream> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { -namespace { - -constexpr char kSIGALRMToMainThread[] = "--itimer_sigarlm_to_main_thread"; -constexpr char kSIGPROFFairnessActive[] = "--itimer_sigprof_fairness_active"; -constexpr char kSIGPROFFairnessIdle[] = "--itimer_sigprof_fairness_idle"; - -// Time period to be set for the itimers. -constexpr absl::Duration kPeriod = absl::Milliseconds(25); -// Total amount of time to spend per thread. -constexpr absl::Duration kTestDuration = absl::Seconds(20); -// Amount of spin iterations to perform as the minimum work item per thread. -// Chosen to be sub-millisecond range. -constexpr int kIterations = 10000000; -// Allow deviation in the number of samples. -constexpr double kNumSamplesDeviationRatio = 0.2; - -TEST(ItimerTest, ItimervalUpdatedBeforeExpiration) { - constexpr int kSleepSecs = 10; - constexpr int kAlarmSecs = 15; - static_assert( - kSleepSecs < kAlarmSecs, - "kSleepSecs must be less than kAlarmSecs for the test to be meaningful"); - constexpr int kMaxRemainingSecs = kAlarmSecs - kSleepSecs; - - // Install a no-op handler for SIGALRM. - struct sigaction sa = {}; - sigfillset(&sa.sa_mask); - sa.sa_handler = +[](int signo) {}; - auto const cleanup_sa = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - // Set an itimer-based alarm for kAlarmSecs from now. - struct itimerval itv = {}; - itv.it_value.tv_sec = kAlarmSecs; - auto const cleanup_itimer = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv)); - - // After sleeping for kSleepSecs, the itimer value should reflect the elapsed - // time even if it hasn't expired. - absl::SleepFor(absl::Seconds(kSleepSecs)); - ASSERT_THAT(getitimer(ITIMER_REAL, &itv), SyscallSucceeds()); - EXPECT_TRUE( - itv.it_value.tv_sec < kMaxRemainingSecs || - (itv.it_value.tv_sec == kMaxRemainingSecs && itv.it_value.tv_usec == 0)) - << "Remaining time: " << itv.it_value.tv_sec << " seconds + " - << itv.it_value.tv_usec << " microseconds"; -} - -ABSL_CONST_INIT static thread_local std::atomic_int signal_test_num_samples = - ATOMIC_VAR_INIT(0); - -void SignalTestSignalHandler(int /*signum*/) { signal_test_num_samples++; } - -struct SignalTestResult { - int expected_total; - int main_thread_samples; - std::vector<int> worker_samples; -}; - -std::ostream& operator<<(std::ostream& os, const SignalTestResult& r) { - os << "{expected_total: " << r.expected_total - << ", main_thread_samples: " << r.main_thread_samples - << ", worker_samples: ["; - bool first = true; - for (int sample : r.worker_samples) { - if (!first) { - os << ", "; - } - os << sample; - first = false; - } - os << "]}"; - return os; -} - -// Starts two worker threads and itimer id and measures the number of signal -// delivered to each thread. -SignalTestResult ItimerSignalTest(int id, clock_t main_clock, - clock_t worker_clock, int signal, - absl::Duration sleep) { - signal_test_num_samples = 0; - - struct sigaction sa = {}; - sa.sa_handler = &SignalTestSignalHandler; - sa.sa_flags = SA_RESTART; - sigemptyset(&sa.sa_mask); - auto sigaction_cleanup = ScopedSigaction(signal, sa).ValueOrDie(); - - int socketfds[2]; - TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, socketfds) == 0); - - // Do the spinning in the workers. - std::function<void*(int)> work = [&](int socket_fd) { - FileDescriptor fd(socket_fd); - - absl::Time finish = Now(worker_clock) + kTestDuration; - while (Now(worker_clock) < finish) { - // Blocked on read. - char c; - RetryEINTR(read)(fd.get(), &c, 1); - for (int i = 0; i < kIterations; i++) { - // Ensure compiler won't optimize this loop away. - asm(""); - } - - if (sleep != absl::ZeroDuration()) { - // Sleep so that the entire process is idle for a while. - absl::SleepFor(sleep); - } - - // Unblock the other thread. - RetryEINTR(write)(fd.get(), &c, 1); - } - - return reinterpret_cast<void*>(signal_test_num_samples.load()); - }; - - ScopedThread th1( - static_cast<std::function<void*()>>(std::bind(work, socketfds[0]))); - ScopedThread th2( - static_cast<std::function<void*()>>(std::bind(work, socketfds[1]))); - - absl::Time start = Now(main_clock); - // Start the timer. - struct itimerval timer = {}; - timer.it_value = absl::ToTimeval(kPeriod); - timer.it_interval = absl::ToTimeval(kPeriod); - auto cleanup_itimer = ScopedItimer(id, timer).ValueOrDie(); - - // Unblock th1. - // - // N.B. th2 owns socketfds[1] but can't close it until it unblocks. - char c = 0; - TEST_CHECK(write(socketfds[1], &c, 1) == 1); - - SignalTestResult result; - - // Wait for the workers to be done and collect their sample counts. - result.worker_samples.push_back(reinterpret_cast<int64_t>(th1.Join())); - result.worker_samples.push_back(reinterpret_cast<int64_t>(th2.Join())); - cleanup_itimer.Release()(); - result.expected_total = (Now(main_clock) - start) / kPeriod; - result.main_thread_samples = signal_test_num_samples.load(); - - return result; -} - -int TestSIGALRMToMainThread() { - SignalTestResult result = - ItimerSignalTest(ITIMER_REAL, CLOCK_REALTIME, CLOCK_REALTIME, SIGALRM, - absl::ZeroDuration()); - - std::cerr << "result: " << result << std::endl; - - // ITIMER_REAL-generated SIGALRMs prefer to deliver to the thread group leader - // (but don't guarantee it), so we expect to see most samples on the main - // thread. - // - // The number of SIGALRMs delivered to a worker should not exceed 20% - // of the number of total signals expected (this is somewhat arbitrary). - const int worker_threshold = result.expected_total / 5; - - // - // Linux only guarantees timers will never expire before the requested time. - // Thus, we only check the upper bound and also it at least have one sample. - TEST_CHECK(result.main_thread_samples <= result.expected_total); - TEST_CHECK(result.main_thread_samples > 0); - for (int num : result.worker_samples) { - TEST_CHECK_MSG(num <= worker_threshold, "worker received too many samples"); - } - - return 0; -} - -// Random save/restore is disabled as it introduces additional latency and -// unpredictable distribution patterns. -TEST(ItimerTest, DeliversSIGALRMToMainThread_NoRandomSave) { - pid_t child; - int execve_errno; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGALRMToMainThread}, - {}, &child, &execve_errno)); - EXPECT_EQ(0, execve_errno); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - - // Not required anymore. - kill.Release(); - - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; -} - -// Signals are delivered to threads fairly. -// -// sleep indicates how long to sleep worker threads each iteration to make the -// entire process idle. -int TestSIGPROFFairness(absl::Duration sleep) { - SignalTestResult result = - ItimerSignalTest(ITIMER_PROF, CLOCK_PROCESS_CPUTIME_ID, - CLOCK_THREAD_CPUTIME_ID, SIGPROF, sleep); - - std::cerr << "result: " << result << std::endl; - - // The number of samples on the main thread should be very low as it did - // nothing. - TEST_CHECK(result.main_thread_samples < 80); - - // Both workers should get roughly equal number of samples. - TEST_CHECK(result.worker_samples.size() == 2); - - TEST_CHECK(result.expected_total > 0); - - // In an ideal world each thread would get exactly 50% of the signals, - // but since that's unlikely to happen we allow for them to get no less than - // kNumSamplesDeviationRatio of the total observed samples. - TEST_CHECK_MSG(std::abs(result.worker_samples[0] - result.worker_samples[1]) < - ((result.worker_samples[0] + result.worker_samples[1]) * - kNumSamplesDeviationRatio), - "one worker received disproportionate share of samples"); - - return 0; -} - -// Random save/restore is disabled as it introduces additional latency and -// unpredictable distribution patterns. -TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyActive_NoRandomSave) { - // On the KVM and ptrace platforms, switches between sentry and application - // context are sometimes extremely slow, causing the itimer to send SIGPROF to - // a thread that either already has one pending or has had SIGPROF delivered, - // but hasn't handled it yet (and thus therefore still has SIGPROF masked). In - // either case, since itimer signals are group-directed, signal sending falls - // back to notifying the thread group leader. ItimerSignalTest() fails if "too - // many" signals are delivered to the thread group leader, so these tests are - // flaky on these platforms. - // - // TODO(b/143247272): Clarify why context switches are so slow on KVM. - const auto gvisor_platform = GvisorPlatform(); - SKIP_IF(gvisor_platform == Platform::kKVM || - gvisor_platform == Platform::kPtrace); - - pid_t child; - int execve_errno; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessActive}, - {}, &child, &execve_errno)); - EXPECT_EQ(0, execve_errno); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - - // Not required anymore. - kill.Release(); - - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -// Random save/restore is disabled as it introduces additional latency and -// unpredictable distribution patterns. -TEST(ItimerTest, DeliversSIGPROFToThreadsRoughlyFairlyIdle_NoRandomSave) { - // See comment in DeliversSIGPROFToThreadsRoughlyFairlyActive. - const auto gvisor_platform = GvisorPlatform(); - SKIP_IF(gvisor_platform == Platform::kKVM || - gvisor_platform == Platform::kPtrace); - - pid_t child; - int execve_errno; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/proc/self/exe", {"/proc/self/exe", kSIGPROFFairnessIdle}, - {}, &child, &execve_errno)); - EXPECT_EQ(0, execve_errno); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - - // Not required anymore. - kill.Release(); - - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Exited with code: " << status; -} - -} // namespace -} // namespace testing -} // namespace gvisor - -namespace { -void MaskSIGPIPE() { - // Always mask SIGPIPE as it's common and tests aren't expected to handle it. - // We don't take the TestInit() path so we must do this manually. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0); -} -} // namespace - -int main(int argc, char** argv) { - // These tests require no background threads, so check for them before - // TestInit. - for (int i = 0; i < argc; i++) { - absl::string_view arg(argv[i]); - - if (arg == gvisor::testing::kSIGALRMToMainThread) { - MaskSIGPIPE(); - return gvisor::testing::TestSIGALRMToMainThread(); - } - if (arg == gvisor::testing::kSIGPROFFairnessActive) { - MaskSIGPIPE(); - return gvisor::testing::TestSIGPROFFairness(absl::ZeroDuration()); - } - if (arg == gvisor::testing::kSIGPROFFairnessIdle) { - MaskSIGPIPE(); - // Sleep time > ClockTick (10ms) exercises sleeping gVisor's - // kernel.cpuClockTicker. - return gvisor::testing::TestSIGPROFFairness(absl::Milliseconds(25)); - } - } - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/kcov.cc b/test/syscalls/linux/kcov.cc deleted file mode 100644 index 6816c1fd0..000000000 --- a/test/syscalls/linux/kcov.cc +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/errno.h> -#include <sys/ioctl.h> -#include <sys/mman.h> - -#include <atomic> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// For this set of tests to run, they must be run with coverage enabled. On -// native Linux, this involves compiling the kernel with kcov enabled. For -// gVisor, we need to enable the Go coverage tool, e.g. bazel test -- -// collect_coverage_data --instrumentation_filter=//pkg/... <test>. - -constexpr char kcovPath[] = "/sys/kernel/debug/kcov"; -constexpr int kSize = 4096; -constexpr int KCOV_INIT_TRACE = 0x80086301; -constexpr int KCOV_ENABLE = 0x6364; -constexpr int KCOV_DISABLE = 0x6365; - -uint64_t* KcovMmap(int fd) { - return (uint64_t*)mmap(nullptr, kSize * sizeof(uint64_t), - PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); -} - -TEST(KcovTest, Kcov) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - int fd; - ASSERT_THAT(fd = open(kcovPath, O_RDWR), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not available. - SKIP_IF(errno == ENOENT); - auto fd_closer = Cleanup([fd]() { close(fd); }); - - ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); - uint64_t* area = KcovMmap(fd); - ASSERT_TRUE(area != MAP_FAILED); - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); - - for (int i = 0; i < 10; i++) { - // Make some syscalls to generate coverage data. - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallFailsWithErrno(EINVAL)); - } - - uint64_t num_pcs = *(uint64_t*)(area); - EXPECT_GT(num_pcs, 0); - for (uint64_t i = 1; i <= num_pcs; i++) { - // Verify that PCs are in the standard kernel range. - EXPECT_GT(area[i], 0xffffffff7fffffffL); - } - - ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds()); -} - -TEST(KcovTest, PrematureMmap) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - int fd; - ASSERT_THAT(fd = open(kcovPath, O_RDWR), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not available. - SKIP_IF(errno == ENOENT); - auto fd_closer = Cleanup([fd]() { close(fd); }); - - // Cannot mmap before KCOV_INIT_TRACE. - uint64_t* area = KcovMmap(fd); - ASSERT_TRUE(area == MAP_FAILED); -} - -// Tests that multiple kcov fds can be used simultaneously. -TEST(KcovTest, MultipleFds) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - int fd1; - ASSERT_THAT(fd1 = open(kcovPath, O_RDWR), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not available. - SKIP_IF(errno == ENOENT); - - int fd2; - ASSERT_THAT(fd2 = open(kcovPath, O_RDWR), SyscallSucceeds()); - auto fd_closer = Cleanup([fd1, fd2]() { - close(fd1); - close(fd2); - }); - - auto t1 = ScopedThread([&] { - ASSERT_THAT(ioctl(fd1, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); - uint64_t* area = KcovMmap(fd1); - ASSERT_TRUE(area != MAP_FAILED); - ASSERT_THAT(ioctl(fd1, KCOV_ENABLE, 0), SyscallSucceeds()); - }); - - ASSERT_THAT(ioctl(fd2, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); - uint64_t* area = KcovMmap(fd2); - ASSERT_TRUE(area != MAP_FAILED); - ASSERT_THAT(ioctl(fd2, KCOV_ENABLE, 0), SyscallSucceeds()); -} - -// Tests behavior for two threads trying to use the same kcov fd. -TEST(KcovTest, MultipleThreads) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - int fd; - ASSERT_THAT(fd = open(kcovPath, O_RDWR), - AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not available. - SKIP_IF(errno == ENOENT); - auto fd_closer = Cleanup([fd]() { close(fd); }); - - // Test the behavior of multiple threads trying to use the same kcov fd - // simultaneously. - std::atomic<bool> t1_enabled(false), t1_disabled(false), t2_failed(false), - t2_exited(false); - auto t1 = ScopedThread([&] { - ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); - uint64_t* area = KcovMmap(fd); - ASSERT_TRUE(area != MAP_FAILED); - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); - t1_enabled = true; - - // After t2 has made sure that enabling kcov again fails, disable it. - while (!t2_failed) { - sched_yield(); - } - ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds()); - t1_disabled = true; - - // Wait for t2 to enable kcov and then exit, after which we should be able - // to enable kcov again, without needing to set up a new memory mapping. - while (!t2_exited) { - sched_yield(); - } - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); - }); - - auto t2 = ScopedThread([&] { - // Wait for t1 to enable kcov, and make sure that enabling kcov again fails. - while (!t1_enabled) { - sched_yield(); - } - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallFailsWithErrno(EINVAL)); - t2_failed = true; - - // Wait for t1 to disable kcov, after which using fd should now succeed. - while (!t1_disabled) { - sched_yield(); - } - uint64_t* area = KcovMmap(fd); - ASSERT_TRUE(area != MAP_FAILED); - ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); - }); - - t2.Join(); - t2_exited = true; -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc deleted file mode 100644 index 5d1735853..000000000 --- a/test/syscalls/linux/kill.cc +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <cerrno> -#include <csignal> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(int32_t, scratch_uid, 65534, "scratch UID"); -ABSL_FLAG(int32_t, scratch_gid, 65534, "scratch GID"); - -using ::testing::Ge; - -namespace gvisor { -namespace testing { - -namespace { - -TEST(KillTest, CanKillValidPid) { - // If pid is positive, then signal sig is sent to the process with the ID - // specified by pid. - EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds()); - // If pid equals 0, then sig is sent to every process in the process group of - // the calling process. - EXPECT_THAT(kill(0, 0), SyscallSucceeds()); - - ScopedThread([] { EXPECT_THAT(kill(gettid(), 0), SyscallSucceeds()); }); -} - -void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); } - -// If pid equals -1, then sig is sent to every process for which the calling -// process has permission to send signals, except for process 1 (init). -TEST(KillTest, CanKillAllPIDs) { - // If we're not running inside the sandbox, then we skip this test - // as our namespace may contain may more processes that cannot tolerate - // the signal below. We also cannot reliably create a new pid namespace - // for ourselves and test the same functionality. - SKIP_IF(!IsRunningOnGvisor()); - - int pipe_fds[2]; - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - FileDescriptor read_fd(pipe_fds[0]); - FileDescriptor write_fd(pipe_fds[1]); - - pid_t pid = fork(); - if (pid == 0) { - read_fd.reset(); - - struct sigaction sa; - sa.sa_sigaction = SigHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - TEST_PCHECK(sigaction(SIGWINCH, &sa, nullptr) == 0); - MaybeSave(); - - // Indicate to the parent that we're ready. - write_fd.reset(); - - // Wait until we get the signal from the parent. - while (true) { - pause(); - } - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - write_fd.reset(); - - // Wait for the child to indicate that it's unmasked the signal by closing - // the write end. - char buf; - ASSERT_THAT(ReadFd(read_fd.get(), &buf, 1), SyscallSucceedsWithValue(0)); - - // Signal the child and wait for it to die with status 0, indicating that - // it got the expected signal. - EXPECT_THAT(kill(-1, SIGWINCH), SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); -} - -TEST(KillTest, CannotKillInvalidPID) { - // We need an unused pid to verify that kill fails when given one. - // - // There is no way to guarantee that a PID is unused, but the PID of a - // recently exited process likely won't be reused soon. - pid_t fake_pid = fork(); - if (fake_pid == 0) { - _exit(0); - } - - ASSERT_THAT(fake_pid, SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(fake_pid, &status, 0), - SyscallSucceedsWithValue(fake_pid)); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); - - EXPECT_THAT(kill(fake_pid, 0), SyscallFailsWithErrno(ESRCH)); -} - -TEST(KillTest, CannotUseInvalidSignal) { - EXPECT_THAT(kill(getpid(), 200), SyscallFailsWithErrno(EINVAL)); -} - -TEST(KillTest, CanKillRemoteProcess) { - pid_t pid = fork(); - if (pid == 0) { - while (true) { - pause(); - } - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(SIGKILL, WTERMSIG(status)); -} - -TEST(KillTest, CanKillOwnProcess) { - EXPECT_THAT(kill(getpid(), 0), SyscallSucceeds()); -} - -// Verify that you can kill a process even using a tid from a thread other than -// the group leader. -TEST(KillTest, CannotKillTid) { - pid_t tid; - bool tid_available = false; - bool finished = false; - absl::Mutex mu; - ScopedThread t([&] { - mu.Lock(); - tid = gettid(); - tid_available = true; - mu.Await(absl::Condition(&finished)); - mu.Unlock(); - }); - mu.LockWhen(absl::Condition(&tid_available)); - EXPECT_THAT(kill(tid, 0), SyscallSucceeds()); - finished = true; - mu.Unlock(); -} - -TEST(KillTest, SetPgid) { - for (int i = 0; i < 10; i++) { - // The following in the normal pattern for creating a new process group. - // Both the parent and child process will call setpgid in order to avoid any - // race conditions. We do this ten times to catch races. - pid_t pid = fork(); - if (pid == 0) { - setpgid(0, 0); - while (true) { - pause(); - } - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - // Set the child's group and exit. - ASSERT_THAT(setpgid(pid, pid), SyscallSucceeds()); - EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(-pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(SIGKILL, WTERMSIG(status)); - } -} - -TEST(KillTest, ProcessGroups) { - // Fork a new child. - // - // other_child is used as a placeholder process. We use this PID as our "does - // not exist" process group to ensure some amount of safety. (It is still - // possible to violate this assumption, but extremely unlikely.) - pid_t child = fork(); - if (child == 0) { - while (true) { - pause(); - } - } - ASSERT_THAT(child, SyscallSucceeds()); - - pid_t other_child = fork(); - if (other_child == 0) { - while (true) { - pause(); - } - } - ASSERT_THAT(other_child, SyscallSucceeds()); - - // Ensure the kill does not succeed without the new group. - EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH)); - - // Put the child in its own process group. - ASSERT_THAT(setpgid(child, child), SyscallSucceeds()); - - // This should be not allowed: you can only create a new group with the same - // id or join an existing one. The other_child group should not exist. - ASSERT_THAT(setpgid(child, other_child), SyscallFailsWithErrno(EPERM)); - - // Done with other_child; kill it. - EXPECT_THAT(kill(other_child, SIGKILL), SyscallSucceeds()); - int status; - EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0), SyscallSucceeds()); - - // Linux returns success for the no-op call. - ASSERT_THAT(setpgid(child, child), SyscallSucceeds()); - - // Kill the child's process group. - ASSERT_THAT(kill(-child, SIGKILL), SyscallSucceeds()); - - // Wait on the process group; ensure that the signal was as expected. - EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(SIGKILL, WTERMSIG(status)); - - // Try to kill the process group again; ensure that the wait fails. - EXPECT_THAT(kill(-child, SIGKILL), SyscallFailsWithErrno(ESRCH)); - EXPECT_THAT(RetryEINTR(waitpid)(-child, &status, 0), - SyscallFailsWithErrno(ECHILD)); -} - -TEST(KillTest, ChildDropsPrivsCannotKill) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - const int uid = absl::GetFlag(FLAGS_scratch_uid); - const int gid = absl::GetFlag(FLAGS_scratch_gid); - - // Create the child that drops privileges and tries to kill the parent. - pid_t pid = fork(); - if (pid == 0) { - TEST_PCHECK(setresgid(gid, gid, gid) == 0); - MaybeSave(); - - TEST_PCHECK(setresuid(uid, uid, uid) == 0); - MaybeSave(); - - // setresuid should have dropped CAP_KILL. Make sure. - TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie()); - - // Try to kill parent with every signal-sending syscall possible. - pid_t parent = getppid(); - - TEST_CHECK(kill(parent, SIGKILL) < 0); - TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(tgkill(parent, parent, SIGKILL) < 0); - TEST_PCHECK_MSG(errno == EPERM, "tgkill failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(syscall(SYS_tkill, parent, SIGKILL) < 0); - TEST_PCHECK_MSG(errno == EPERM, "tkill failed with wrong errno"); - MaybeSave(); - - siginfo_t uinfo; - uinfo.si_code = -1; // SI_QUEUE (allowed). - - TEST_CHECK(syscall(SYS_rt_sigqueueinfo, parent, SIGKILL, &uinfo) < 0); - TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(syscall(SYS_rt_tgsigqueueinfo, parent, parent, SIGKILL, &uinfo) < - 0); - TEST_PCHECK_MSG(errno == EPERM, "rt_sigqueueinfo failed with wrong errno"); - MaybeSave(); - - _exit(0); - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status = " << status; -} - -TEST(KillTest, CanSIGCONTSameSession) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - pid_t stopped_child = fork(); - if (stopped_child == 0) { - raise(SIGSTOP); - _exit(0); - } - - ASSERT_THAT(stopped_child, SyscallSucceeds()); - - // Put the child in its own process group. The child and parent process - // groups also share a session. - ASSERT_THAT(setpgid(stopped_child, stopped_child), SyscallSucceeds()); - - // Make sure child stopped. - int status; - EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, WUNTRACED), - SyscallSucceedsWithValue(stopped_child)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << "status " << status; - - const int uid = absl::GetFlag(FLAGS_scratch_uid); - const int gid = absl::GetFlag(FLAGS_scratch_gid); - - // Drop privileges only in child process, or else this parent process won't be - // able to open some log files after the test ends. - pid_t other_child = fork(); - if (other_child == 0) { - // Drop privileges. - TEST_PCHECK(setresgid(gid, gid, gid) == 0); - MaybeSave(); - - TEST_PCHECK(setresuid(uid, uid, uid) == 0); - MaybeSave(); - - // setresuid should have dropped CAP_KILL. - TEST_CHECK(!HaveCapability(CAP_KILL).ValueOrDie()); - - // Child 2 and child should now not share a thread group and any UIDs. - // Child 2 should have no privileges. That means any signal other than - // SIGCONT should fail. - TEST_CHECK(kill(stopped_child, SIGKILL) < 0); - TEST_PCHECK_MSG(errno == EPERM, "kill failed with wrong errno"); - MaybeSave(); - - TEST_PCHECK(kill(stopped_child, SIGCONT) == 0); - MaybeSave(); - - _exit(0); - } - - ASSERT_THAT(stopped_child, SyscallSucceeds()); - - // Make sure child exited normally. - EXPECT_THAT(RetryEINTR(waitpid)(stopped_child, &status, 0), - SyscallSucceedsWithValue(stopped_child)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; - - // Make sure other_child exited normally. - EXPECT_THAT(RetryEINTR(waitpid)(other_child, &status, 0), - SyscallSucceedsWithValue(other_child)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc deleted file mode 100644 index 4f9ca1a65..000000000 --- a/test/syscalls/linux/link.cc +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/strings/str_cat.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(int32_t, scratch_uid, 65534, "scratch UID"); - -namespace gvisor { -namespace testing { - -namespace { - -// IsSameFile returns true if both filenames have the same device and inode. -bool IsSameFile(const std::string& f1, const std::string& f2) { - // Use lstat rather than stat, so that symlinks are not followed. - struct stat stat1 = {}; - EXPECT_THAT(lstat(f1.c_str(), &stat1), SyscallSucceeds()); - struct stat stat2 = {}; - EXPECT_THAT(lstat(f2.c_str(), &stat2), SyscallSucceeds()); - - return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; -} - -// TODO(b/178640646): Add test for linkat with AT_EMPTY_PATH - -TEST(LinkTest, CanCreateLinkFile) { - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newname = NewTempAbsPath(); - - // Get the initial link count. - uint64_t initial_link_count = - ASSERT_NO_ERRNO_AND_VALUE(Links(oldfile.path())); - - EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()), SyscallSucceeds()); - - EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); - - // Link count should be incremented. - EXPECT_THAT(Links(oldfile.path()), - IsPosixErrorOkAndHolds(initial_link_count + 1)); - - // Delete the link. - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); - - // Link count should be back to initial. - EXPECT_THAT(Links(oldfile.path()), - IsPosixErrorOkAndHolds(initial_link_count)); -} - -TEST(LinkTest, PermissionDenied) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER))); - - // Make the file "unsafe" to link by making it only readable, but not - // writable. - const auto unwriteable_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400)); - const std::string special_path = NewTempAbsPath(); - ASSERT_THAT(mkfifo(special_path.c_str(), 0666), SyscallSucceeds()); - const auto setuid_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666 | S_ISUID)); - - const std::string newname = NewTempAbsPath(); - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. After calling - // setuid(non-zero-UID), there is no way to get root privileges back. - ScopedThread([&] { - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. POSIX threads, however, require that all - // threads have the same UIDs, so using the setuid wrapper sets all threads' - // real UID. - // Also drops capabilities. - EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)), - SyscallSucceeds()); - - EXPECT_THAT(link(unwriteable_file.path().c_str(), newname.c_str()), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(link(special_path.c_str(), newname.c_str()), - SyscallFailsWithErrno(EPERM)); - if (!IsRunningWithVFS1()) { - EXPECT_THAT(link(setuid_file.path().c_str(), newname.c_str()), - SyscallFailsWithErrno(EPERM)); - } - }); -} - -TEST(LinkTest, CannotLinkDirectory) { - auto olddir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string newdir = NewTempAbsPath(); - - EXPECT_THAT(link(olddir.path().c_str(), newdir.c_str()), - SyscallFailsWithErrno(EPERM)); - - EXPECT_THAT(rmdir(olddir.path().c_str()), SyscallSucceeds()); -} - -TEST(LinkTest, CannotLinkWithSlash) { - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - // Put a final "/" on newname. - const std::string newname = absl::StrCat(NewTempAbsPath(), "/"); - - EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(LinkTest, OldnameIsEmpty) { - const std::string newname = NewTempAbsPath(); - EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT)); -} - -TEST(LinkTest, OldnameDoesNotExist) { - const std::string oldname = NewTempAbsPath(); - const std::string newname = NewTempAbsPath(); - EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT)); -} - -TEST(LinkTest, NewnameCannotExist) { - const std::string newname = - JoinPath(GetAbsoluteTestTmpdir(), "thisdoesnotexist", "foo"); - EXPECT_THAT(link("/thisdoesnotmatter", newname.c_str()), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(LinkTest, WithOldDirFD) { - const std::string oldname_parent = NewTempAbsPath(); - const std::string oldname_base = "child"; - const std::string oldname = JoinPath(oldname_parent, oldname_base); - const std::string newname = NewTempAbsPath(); - - // Create oldname_parent directory, and get an FD. - ASSERT_THAT(mkdir(oldname_parent.c_str(), 0777), SyscallSucceeds()); - const FileDescriptor oldname_parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(oldname_parent, O_DIRECTORY | O_RDONLY)); - - // Create oldname file. - const FileDescriptor oldname_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(oldname, O_CREAT | O_RDWR, 0666)); - - // Link oldname to newname, using oldname_parent_fd. - EXPECT_THAT(linkat(oldname_parent_fd.get(), oldname_base.c_str(), AT_FDCWD, - newname.c_str(), 0), - SyscallSucceeds()); - - EXPECT_TRUE(IsSameFile(oldname, newname)); - - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds()); - EXPECT_THAT(rmdir(oldname_parent.c_str()), SyscallSucceeds()); -} - -TEST(LinkTest, BogusFlags) { - ASSERT_THAT(linkat(1, "foo", 2, "bar", 3), SyscallFailsWithErrno(EINVAL)); -} - -TEST(LinkTest, WithNewDirFD) { - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newname_parent = NewTempAbsPath(); - const std::string newname_base = "child"; - const std::string newname = JoinPath(newname_parent, newname_base); - - // Create newname_parent directory, and get an FD. - EXPECT_THAT(mkdir(newname_parent.c_str(), 0777), SyscallSucceeds()); - const FileDescriptor newname_parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(newname_parent, O_DIRECTORY | O_RDONLY)); - - // Link newname to oldfile, using newname_parent_fd. - EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), newname_parent_fd.get(), - newname.c_str(), 0), - SyscallSucceeds()); - - EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); - - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); - EXPECT_THAT(rmdir(newname_parent.c_str()), SyscallSucceeds()); -} - -TEST(LinkTest, RelPathsWithNonDirFDs) { - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Create a file that will be passed as the directory fd for old/new names. - const std::string filename = NewTempAbsPath(); - const FileDescriptor file_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0666)); - - // Using file_fd as olddirfd will fail. - EXPECT_THAT(linkat(file_fd.get(), "foo", AT_FDCWD, "bar", 0), - SyscallFailsWithErrno(ENOTDIR)); - - // Using file_fd as newdirfd will fail. - EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), file_fd.get(), "bar", 0), - SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(LinkTest, AbsPathsWithNonDirFDs) { - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newname = NewTempAbsPath(); - - // Create a file that will be passed as the directory fd for old/new names. - const std::string filename = NewTempAbsPath(); - const FileDescriptor file_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0666)); - - // Using file_fd as the dirfds is OK as long as paths are absolute. - EXPECT_THAT(linkat(file_fd.get(), oldfile.path().c_str(), file_fd.get(), - newname.c_str(), 0), - SyscallSucceeds()); -} - -TEST(LinkTest, NewDirFDWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newname_parent = NewTempAbsPath(); - const std::string newname_base = "child"; - const std::string newname = JoinPath(newname_parent, newname_base); - - // Create newname_parent directory, and get an FD. - EXPECT_THAT(mkdir(newname_parent.c_str(), 0777), SyscallSucceeds()); - const FileDescriptor newname_parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(newname_parent, O_DIRECTORY | O_PATH)); - - // Link newname to oldfile, using newname_parent_fd. - EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), newname_parent_fd.get(), - newname.c_str(), 0), - SyscallSucceeds()); - - EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); -} - -TEST(LinkTest, RelPathsNonDirFDsWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Create a file that will be passed as the directory fd for old/new names. - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - - // Using file_fd as olddirfd will fail. - EXPECT_THAT(linkat(file_fd.get(), "foo", AT_FDCWD, "bar", 0), - SyscallFailsWithErrno(ENOTDIR)); - - // Using file_fd as newdirfd will fail. - EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), file_fd.get(), "bar", 0), - SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(LinkTest, AbsPathsNonDirFDsWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newname = NewTempAbsPath(); - - // Create a file that will be passed as the directory fd for old/new names. - TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); - - // Using file_fd as the dirfds is OK as long as paths are absolute. - EXPECT_THAT(linkat(file_fd.get(), oldfile.path().c_str(), file_fd.get(), - newname.c_str(), 0), - SyscallSucceeds()); -} - -TEST(LinkTest, LinkDoesNotFollowSymlinks) { - // Create oldfile, and oldsymlink which points to it. - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string oldsymlink = NewTempAbsPath(); - EXPECT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()), - SyscallSucceeds()); - - // Now hard link newname to oldsymlink. - const std::string newname = NewTempAbsPath(); - EXPECT_THAT(link(oldsymlink.c_str(), newname.c_str()), SyscallSucceeds()); - - // The link should not have resolved the symlink, so newname and oldsymlink - // are the same. - EXPECT_TRUE(IsSameFile(oldsymlink, newname)); - EXPECT_FALSE(IsSameFile(oldfile.path(), newname)); - - EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); -} - -TEST(LinkTest, LinkatDoesNotFollowSymlinkByDefault) { - // Create oldfile, and oldsymlink which points to it. - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string oldsymlink = NewTempAbsPath(); - EXPECT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()), - SyscallSucceeds()); - - // Now hard link newname to oldsymlink. - const std::string newname = NewTempAbsPath(); - EXPECT_THAT( - linkat(AT_FDCWD, oldsymlink.c_str(), AT_FDCWD, newname.c_str(), 0), - SyscallSucceeds()); - - // The link should not have resolved the symlink, so newname and oldsymlink - // are the same. - EXPECT_TRUE(IsSameFile(oldsymlink, newname)); - EXPECT_FALSE(IsSameFile(oldfile.path(), newname)); - - EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); -} - -TEST(LinkTest, LinkatWithSymlinkFollow) { - // Create oldfile, and oldsymlink which points to it. - auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string oldsymlink = NewTempAbsPath(); - ASSERT_THAT(symlink(oldfile.path().c_str(), oldsymlink.c_str()), - SyscallSucceeds()); - - // Now hard link newname to oldsymlink, and pass AT_SYMLINK_FOLLOW flag. - const std::string newname = NewTempAbsPath(); - ASSERT_THAT(linkat(AT_FDCWD, oldsymlink.c_str(), AT_FDCWD, newname.c_str(), - AT_SYMLINK_FOLLOW), - SyscallSucceeds()); - - // The link should have resolved the symlink, so oldfile and newname are the - // same. - EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); - EXPECT_FALSE(IsSameFile(oldsymlink, newname)); - - EXPECT_THAT(unlink(oldsymlink.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc deleted file mode 100644 index 6ce1e6cc3..000000000 --- a/test/syscalls/linux/lseek.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(LseekTest, InvalidWhence) { - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - - ASSERT_THAT(lseek(fd.get(), 0, -1), SyscallFailsWithErrno(EINVAL)); -} - -TEST(LseekTest, NegativeOffset) { - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - - EXPECT_THAT(lseek(fd.get(), -(kFileData.length() + 1), SEEK_CUR), - SyscallFailsWithErrno(EINVAL)); -} - -// A 32-bit off_t is not large enough to represent an offset larger than -// maximum file size on standard file systems, so it isn't possible to cause -// overflow. -#if defined(__x86_64__) || defined(__aarch64__) -TEST(LseekTest, Overflow) { - // HA! Classic Linux. We really should have an EOVERFLOW - // here, since we're seeking to something that cannot be - // represented.. but instead we are given an EINVAL. - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - EXPECT_THAT(lseek(fd.get(), 0x7fffffffffffffff, SEEK_END), - SyscallFailsWithErrno(EINVAL)); -} -#endif - -TEST(LseekTest, Set) { - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - - char buf = '\0'; - EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[0]); - EXPECT_THAT(lseek(fd.get(), 6, SEEK_SET), SyscallSucceedsWithValue(6)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[6]); -} - -TEST(LseekTest, Cur) { - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - - char buf = '\0'; - EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[0]); - EXPECT_THAT(lseek(fd.get(), 3, SEEK_CUR), SyscallSucceedsWithValue(4)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[4]); -} - -TEST(LseekTest, End) { - const std::string kFileData = "hello world\n"; - const TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kFileData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDWR, 0644)); - - char buf = '\0'; - EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[0]); - EXPECT_THAT(lseek(fd.get(), -2, SEEK_END), SyscallSucceedsWithValue(10)); - ASSERT_THAT(read(fd.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, kFileData.c_str()[kFileData.length() - 2]); -} - -TEST(LseekTest, InvalidFD) { - EXPECT_THAT(lseek(-1, 0, SEEK_SET), SyscallFailsWithErrno(EBADF)); -} - -TEST(LseekTest, DirCurEnd) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY)); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); -} - -TEST(LseekTest, ProcDir) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self", O_RDONLY)); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); -} - -TEST(LseekTest, ProcFile) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/meminfo", O_RDONLY)); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL)); -} - -TEST(LseekTest, SysDir) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/sys/devices", O_RDONLY)); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), SyscallSucceeds()); -} - -TEST(LseekTest, SeekCurrentDir) { - // From include/linux/fs.h. - constexpr loff_t MAX_LFS_FILESIZE = 0x7fffffffffffffff; - - char* dir = get_current_dir_name(); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir, O_RDONLY)); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - ASSERT_THAT(lseek(fd.get(), 0, SEEK_END), - // Some filesystems (like ext4) allow lseek(SEEK_END) on a - // directory and return MAX_LFS_FILESIZE, others return EINVAL. - AnyOf(SyscallSucceedsWithValue(MAX_LFS_FILESIZE), - SyscallFailsWithErrno(EINVAL))); - free(dir); -} - -TEST(LseekTest, ProcStatTwice) { - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY)); - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY)); - - ASSERT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - ASSERT_THAT(lseek(fd1.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceeds()); - // Check that just because we moved fd1, fd2 doesn't move. - ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - const FileDescriptor fd3 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/stat", O_RDONLY)); - ASSERT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); -} - -TEST(LseekTest, EtcPasswdDup) { - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/etc/passwd", O_RDONLY)); - const FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(fd1.Dup()); - - ASSERT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - ASSERT_THAT(lseek(fd1.get(), 1000, SEEK_CUR), SyscallSucceeds()); - // Check that just because we moved fd1, fd2 doesn't move. - ASSERT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(1000)); - - const FileDescriptor fd3 = ASSERT_NO_ERRNO_AND_VALUE(fd1.Dup()); - ASSERT_THAT(lseek(fd3.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(1000)); -} - -// TODO(magi): Add tests where we have donated in sockets. - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/madvise.cc b/test/syscalls/linux/madvise.cc deleted file mode 100644 index 6e714b12c..000000000 --- a/test/syscalls/linux/madvise.cc +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <string> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void ExpectAllMappingBytes(Mapping const& m, char c) { - auto const v = m.view(); - for (size_t i = 0; i < kPageSize; i++) { - ASSERT_EQ(v[i], c) << "at offset " << i; - } -} - -// Equivalent to ExpectAllMappingBytes but async-signal-safe and with less -// helpful failure messages. -void CheckAllMappingBytes(Mapping const& m, char c) { - auto const v = m.view(); - for (size_t i = 0; i < kPageSize; i++) { - TEST_CHECK_MSG(v[i] == c, "mapping contains wrong value"); - } -} - -TEST(MadviseDontneedTest, ZerosPrivateAnonPage) { - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - ExpectAllMappingBytes(m, 0); - memset(m.ptr(), 1, m.len()); - ExpectAllMappingBytes(m, 1); - ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); - ExpectAllMappingBytes(m, 0); -} - -TEST(MadviseDontneedTest, ZerosCOWAnonPageInCallerOnly) { - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - ExpectAllMappingBytes(m, 0); - memset(m.ptr(), 2, m.len()); - ExpectAllMappingBytes(m, 2); - - // Do madvise in a child process. - pid_t pid = fork(); - CheckAllMappingBytes(m, 2); - if (pid == 0) { - TEST_PCHECK(madvise(m.ptr(), m.len(), MADV_DONTNEED) == 0); - CheckAllMappingBytes(m, 0); - _exit(0); - } - - ASSERT_THAT(pid, SyscallSucceeds()); - - int status = 0; - ASSERT_THAT(waitpid(-1, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(WEXITSTATUS(status), 0); - // The child's madvise should not have affected the parent. - ExpectAllMappingBytes(m, 2); -} - -TEST(MadviseDontneedTest, DoesNotModifySharedAnonPage) { - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); - ExpectAllMappingBytes(m, 0); - memset(m.ptr(), 3, m.len()); - ExpectAllMappingBytes(m, 3); - ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); - ExpectAllMappingBytes(m, 3); -} - -TEST(MadviseDontneedTest, CleansPrivateFilePage) { - TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - /* parent = */ GetAbsoluteTestTmpdir(), - /* content = */ std::string(kPageSize, 4), TempPath::kDefaultFileMode)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - - Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0)); - ExpectAllMappingBytes(m, 4); - memset(m.ptr(), 5, m.len()); - ExpectAllMappingBytes(m, 5); - ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); - ExpectAllMappingBytes(m, 4); -} - -TEST(MadviseDontneedTest, DoesNotModifySharedFilePage) { - TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - /* parent = */ GetAbsoluteTestTmpdir(), - /* content = */ std::string(kPageSize, 6), TempPath::kDefaultFileMode)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - - Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - ExpectAllMappingBytes(m, 6); - memset(m.ptr(), 7, m.len()); - ExpectAllMappingBytes(m, 7); - ASSERT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); - ExpectAllMappingBytes(m, 7); -} - -TEST(MadviseDontneedTest, IgnoresPermissions) { - auto m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); - EXPECT_THAT(madvise(m.ptr(), m.len(), MADV_DONTNEED), SyscallSucceeds()); -} - -TEST(MadviseDontforkTest, AddressLength) { - auto m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); - char* addr = static_cast<char*>(m.ptr()); - - // Address must be page aligned. - EXPECT_THAT(madvise(addr + 1, kPageSize, MADV_DONTFORK), - SyscallFailsWithErrno(EINVAL)); - - // Zero length madvise always succeeds. - EXPECT_THAT(madvise(addr, 0, MADV_DONTFORK), SyscallSucceeds()); - - // Length must not roll over after rounding up. - size_t badlen = std::numeric_limits<std::size_t>::max() - (kPageSize / 2); - EXPECT_THAT(madvise(0, badlen, MADV_DONTFORK), SyscallFailsWithErrno(EINVAL)); - - // Length need not be page aligned - it is implicitly rounded up. - EXPECT_THAT(madvise(addr, 1, MADV_DONTFORK), SyscallSucceeds()); - EXPECT_THAT(madvise(addr, kPageSize, MADV_DONTFORK), SyscallSucceeds()); -} - -TEST(MadviseDontforkTest, DontforkShared) { - // Mmap two shared file-backed pages and MADV_DONTFORK the second page. - TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - /* parent = */ GetAbsoluteTestTmpdir(), - /* content = */ std::string(kPageSize * 2, 2), - TempPath::kDefaultFileMode)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - - Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - - const Mapping ms1 = Mapping(reinterpret_cast<void*>(m.addr()), kPageSize); - const Mapping ms2 = - Mapping(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize); - m.release(); - - ASSERT_THAT(madvise(ms2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds()); - - const auto rest = [&] { - // First page is mapped in child and modifications are visible to parent - // via the shared mapping. - TEST_CHECK(IsMapped(ms1.addr())); - CheckAllMappingBytes(ms1, 2); - memset(ms1.ptr(), 1, kPageSize); - CheckAllMappingBytes(ms1, 1); - - // Second page must not be mapped in child. - TEST_CHECK(!IsMapped(ms2.addr())); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); - - ExpectAllMappingBytes(ms1, 1); // page contents modified by child. - ExpectAllMappingBytes(ms2, 2); // page contents unchanged. -} - -TEST(MadviseDontforkTest, DontforkAnonPrivate) { - // Mmap three anonymous pages and MADV_DONTFORK the middle page. - Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - const Mapping mp1 = Mapping(reinterpret_cast<void*>(m.addr()), kPageSize); - const Mapping mp2 = - Mapping(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize); - const Mapping mp3 = - Mapping(reinterpret_cast<void*>(m.addr() + 2 * kPageSize), kPageSize); - m.release(); - - ASSERT_THAT(madvise(mp2.ptr(), kPageSize, MADV_DONTFORK), SyscallSucceeds()); - - // Verify that all pages are zeroed and memset the first, second and third - // pages to 1, 2, and 3 respectively. - ExpectAllMappingBytes(mp1, 0); - memset(mp1.ptr(), 1, kPageSize); - - ExpectAllMappingBytes(mp2, 0); - memset(mp2.ptr(), 2, kPageSize); - - ExpectAllMappingBytes(mp3, 0); - memset(mp3.ptr(), 3, kPageSize); - - const auto rest = [&] { - // Verify first page is mapped, verify its contents and then modify the - // page. The mapping is private so the modifications are not visible to - // the parent. - TEST_CHECK(IsMapped(mp1.addr())); - CheckAllMappingBytes(mp1, 1); - memset(mp1.ptr(), 11, kPageSize); - CheckAllMappingBytes(mp1, 11); - - // Verify second page is not mapped. - TEST_CHECK(!IsMapped(mp2.addr())); - - // Verify third page is mapped, verify its contents and then modify the - // page. The mapping is private so the modifications are not visible to - // the parent. - TEST_CHECK(IsMapped(mp3.addr())); - CheckAllMappingBytes(mp3, 3); - memset(mp3.ptr(), 13, kPageSize); - CheckAllMappingBytes(mp3, 13); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); - - // The fork and COW by child should not affect the parent mappings. - ExpectAllMappingBytes(mp1, 1); - ExpectAllMappingBytes(mp2, 2); - ExpectAllMappingBytes(mp3, 3); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/membarrier.cc b/test/syscalls/linux/membarrier.cc deleted file mode 100644 index 516956a25..000000000 --- a/test/syscalls/linux/membarrier.cc +++ /dev/null @@ -1,268 +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. - -#include <errno.h> -#include <signal.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <atomic> - -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// This is the classic test case for memory fences on architectures with total -// store ordering; see e.g. Intel SDM Vol. 3A Sec. 8.2.3.4 "Loads May Be -// Reordered with Earlier Stores to Different Locations". In each iteration of -// the test, given two variables X and Y initially set to 0 -// (MembarrierTestSharedState::local_var and remote_var in the code), two -// threads execute as follows: -// -// T1 T2 -// -- -- -// -// X = 1 Y = 1 -// T1fence() T2fence() -// read Y read X -// -// On architectures where memory writes may be locally buffered by each CPU -// (essentially all architectures), if T1fence() and T2fence() are omitted or -// ineffective, it is possible for both T1 and T2 to read 0 because the memory -// write from the other CPU is not yet visible outside that CPU. T1fence() and -// T2fence() are expected to perform the necessary synchronization to restore -// sequential consistency: both threads agree on a order of memory accesses that -// is consistent with program order in each thread, such that at least one -// thread reads 1. -// -// In the NoMembarrier test, T1fence() and T2fence() are both ordinary memory -// fences establishing ordering between memory accesses before and after the -// fence (std::atomic_thread_fence). In all other test cases, T1fence() is not a -// memory fence at all, but only prevents compiler reordering of memory accesses -// (std::atomic_signal_fence); T2fence() is an invocation of the membarrier() -// syscall, which establishes ordering of memory accesses before and after the -// syscall on both threads. - -template <typename F> -int DoMembarrierTestSide(std::atomic<int>* our_var, - std::atomic<int> const& their_var, - F const& test_fence) { - our_var->store(1, std::memory_order_relaxed); - test_fence(); - return their_var.load(std::memory_order_relaxed); -} - -struct MembarrierTestSharedState { - std::atomic<int64_t> remote_iter_cur; - std::atomic<int64_t> remote_iter_done; - std::atomic<int> local_var; - std::atomic<int> remote_var; - int remote_obs_of_local_var; - - void Init() { - remote_iter_cur.store(-1, std::memory_order_relaxed); - remote_iter_done.store(-1, std::memory_order_relaxed); - } -}; - -// Special value for MembarrierTestSharedState::remote_iter_cur indicating that -// the remote thread should terminate. -constexpr int64_t kRemoteIterStop = -2; - -// Must be async-signal-safe. -template <typename F> -void RunMembarrierTestRemoteSide(MembarrierTestSharedState* state, - F const& test_fence) { - int64_t i = 0; - int64_t cur; - while (true) { - while ((cur = state->remote_iter_cur.load(std::memory_order_acquire)) < i) { - if (cur == kRemoteIterStop) { - return; - } - // spin - } - state->remote_obs_of_local_var = - DoMembarrierTestSide(&state->remote_var, state->local_var, test_fence); - state->remote_iter_done.store(i, std::memory_order_release); - i++; - } -} - -template <typename F> -void RunMembarrierTestLocalSide(MembarrierTestSharedState* state, - F const& test_fence) { - // On test completion, instruct the remote thread to terminate. - Cleanup cleanup_remote([&] { - state->remote_iter_cur.store(kRemoteIterStop, std::memory_order_relaxed); - }); - - int64_t i = 0; - absl::Time end = absl::Now() + absl::Seconds(5); // arbitrary test duration - while (absl::Now() < end) { - // Reset both vars to 0. - state->local_var.store(0, std::memory_order_relaxed); - state->remote_var.store(0, std::memory_order_relaxed); - // Instruct the remote thread to begin this iteration. - state->remote_iter_cur.store(i, std::memory_order_release); - // Perform our side of the test. - auto local_obs_of_remote_var = - DoMembarrierTestSide(&state->local_var, state->remote_var, test_fence); - // Wait for the remote thread to finish this iteration. - while (state->remote_iter_done.load(std::memory_order_acquire) < i) { - // spin - } - ASSERT_TRUE(local_obs_of_remote_var != 0 || - state->remote_obs_of_local_var != 0); - i++; - } -} - -TEST(MembarrierTest, NoMembarrier) { - MembarrierTestSharedState state; - state.Init(); - - ScopedThread remote_thread([&] { - RunMembarrierTestRemoteSide( - &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); - }); - RunMembarrierTestLocalSide( - &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); -} - -enum membarrier_cmd { - MEMBARRIER_CMD_QUERY = 0, - MEMBARRIER_CMD_GLOBAL = (1 << 0), - MEMBARRIER_CMD_GLOBAL_EXPEDITED = (1 << 1), - MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = (1 << 2), - MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3), - MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4), -}; - -int membarrier(membarrier_cmd cmd, int flags) { - return syscall(SYS_membarrier, cmd, flags); -} - -PosixErrorOr<int> SupportedMembarrierCommands() { - int cmds = membarrier(MEMBARRIER_CMD_QUERY, 0); - if (cmds < 0) { - if (errno == ENOSYS) { - // No commands are supported. - return 0; - } - return PosixError(errno, "membarrier(MEMBARRIER_CMD_QUERY) failed"); - } - return cmds; -} - -TEST(MembarrierTest, Global) { - SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & - MEMBARRIER_CMD_GLOBAL) == 0); - - Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); - auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); - state->Init(); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - RunMembarrierTestRemoteSide( - state, [] { TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL, 0) == 0); }); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - Cleanup cleanup_child([&] { - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - }); - RunMembarrierTestLocalSide( - state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); -} - -TEST(MembarrierTest, GlobalExpedited) { - constexpr int kRequiredCommands = MEMBARRIER_CMD_GLOBAL_EXPEDITED | - MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED; - SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & - kRequiredCommands) != kRequiredCommands); - - ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0), - SyscallSucceeds()); - - Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); - auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); - state->Init(); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - RunMembarrierTestRemoteSide(state, [] { - TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL_EXPEDITED, 0) == 0); - }); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - Cleanup cleanup_child([&] { - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - }); - RunMembarrierTestLocalSide( - state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); -} - -TEST(MembarrierTest, PrivateExpedited) { - constexpr int kRequiredCommands = MEMBARRIER_CMD_PRIVATE_EXPEDITED | - MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED; - SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & - kRequiredCommands) != kRequiredCommands); - - ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0), - SyscallSucceeds()); - - MembarrierTestSharedState state; - state.Init(); - - ScopedThread remote_thread([&] { - RunMembarrierTestRemoteSide(&state, [] { - TEST_PCHECK(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0) == 0); - }); - }); - RunMembarrierTestLocalSide( - &state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc deleted file mode 100644 index 4a450742b..000000000 --- a/test/syscalls/linux/memfd.cc +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/memfd.h> -#include <linux/unistd.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/syscall.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -// The header sys/memfd.h isn't available on all systems, so redefining some of -// the constants here. -#define F_LINUX_SPECIFIC_BASE 1024 - -#ifndef F_ADD_SEALS -#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) -#endif /* F_ADD_SEALS */ - -#ifndef F_GET_SEALS -#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) -#endif /* F_GET_SEALS */ - -#define F_SEAL_SEAL 0x0001 -#define F_SEAL_SHRINK 0x0002 -#define F_SEAL_GROW 0x0004 -#define F_SEAL_WRITE 0x0008 - -using ::gvisor::testing::IsTmpfs; -using ::testing::StartsWith; - -const std::string kMemfdName = "some-memfd"; - -int memfd_create(const std::string& name, unsigned int flags) { - return syscall(__NR_memfd_create, name.c_str(), flags); -} - -PosixErrorOr<FileDescriptor> MemfdCreate(const std::string& name, - uint32_t flags) { - int fd = memfd_create(name, flags); - if (fd < 0) { - return PosixError( - errno, absl::StrFormat("memfd_create(\"%s\", %#x)", name, flags)); - } - MaybeSave(); - return FileDescriptor(fd); -} - -// Procfs entries for memfds display the appropriate name. -TEST(MemfdTest, Name) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - const std::string proc_name = ASSERT_NO_ERRNO_AND_VALUE( - ReadLink(absl::StrFormat("/proc/self/fd/%d", memfd.get()))); - EXPECT_THAT(proc_name, StartsWith("/memfd:" + kMemfdName)); -} - -// Memfds support read/write syscalls. -TEST(MemfdTest, WriteRead) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - - // Write a random page of data to the memfd via write(2). - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Read back the same data and verify. - std::vector<char> buf2(kPageSize); - ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(buf, buf2); -} - -// Memfds can be mapped and used as usual. -TEST(MemfdTest, Mmap) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - - // Write a random page of data to the memfd via mmap m1. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - memcpy(m1.ptr(), buf.data(), buf.size()); - - // Read the data back via a read syscall on the memfd. - std::vector<char> buf2(kPageSize); - EXPECT_THAT(read(memfd.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(buf, buf2); - - // The same data should be accessible via a new mapping m2. - const Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - EXPECT_EQ(0, memcmp(m1.ptr(), m2.ptr(), kPageSize)); -} - -TEST(MemfdTest, DuplicateFDsShareContent) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - const Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup()); - - // Write a random page of data to the memfd via mmap m1. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - memcpy(m1.ptr(), buf.data(), buf.size()); - - // Read the data back via a read syscall on a duplicate fd. - std::vector<char> buf2(kPageSize); - EXPECT_THAT(read(memfd2.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(buf, buf2); -} - -// File seals are disabled by default on memfds. -TEST(MemfdTest, SealingDisabledByDefault) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_SEAL)); - // Attempting to set any seal should fail. - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), - SyscallFailsWithErrno(EPERM)); -} - -// Seals can be retrieved and updated for memfds. -TEST(MemfdTest, SealsGetSet) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - int seals; - ASSERT_THAT(seals = fcntl(memfd.get(), F_GET_SEALS), SyscallSucceeds()); - // No seals are set yet. - EXPECT_EQ(0, seals); - - // Set a seal and check that we can get it back. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_WRITE)); - - // Set some more seals and verify. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK), - SyscallSucceeds()); - EXPECT_THAT( - fcntl(memfd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK)); - - // Attempting to set a seal that is already set is a no-op. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - EXPECT_THAT( - fcntl(memfd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK)); - - // Add remaining seals and verify. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SEAL), SyscallSucceeds()); - EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_WRITE | F_SEAL_GROW | - F_SEAL_SHRINK | F_SEAL_SEAL)); -} - -// F_SEAL_GROW prevents a memfd from being grown using ftruncate. -TEST(MemfdTest, SealGrowWithTruncate) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds()); - - // Try grow the memfd by 1 page. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2), - SyscallFailsWithErrno(EPERM)); - - // Ftruncate calls that don't actually grow the memfd are allowed. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2), SyscallSucceeds()); - - // After shrinking, growing back is not allowed. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM)); -} - -// F_SEAL_GROW prevents a memfd from being grown using the write syscall. -TEST(MemfdTest, SealGrowWithWrite) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - - // Initially, writing to the memfd succeeds. - const std::vector<char> buf(kPageSize); - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Apply F_SEAL_GROW, subsequent writes which extend the memfd should fail. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds()); - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPERM)); - - // However, zero-length writes are ok since they don't grow the memfd. - EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds()); - - // Writing to existing parts of the memfd is also ok. - ASSERT_THAT(lseek(memfd.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Returning the end of the file and writing still not allowed. - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPERM)); -} - -// F_SEAL_GROW causes writes which partially extend off the current EOF to -// partially succeed, up to the page containing the EOF. -TEST(MemfdTest, SealGrowPartialWriteTruncated) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds()); - - // FD offset: 1 page, EOF: 1 page. - - ASSERT_THAT(lseek(memfd.get(), kPageSize * 3 / 4, SEEK_SET), - SyscallSucceeds()); - - // FD offset: 3/4 page. Writing a full page now should only write 1/4 page - // worth of data. This partially succeeds because the first page is entirely - // within the file and requires no growth, but attempting to write the final - // 3/4 page would require growing the file. - const std::vector<char> buf(kPageSize); - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize / 4)); -} - -// F_SEAL_GROW causes writes which partially extend off the current EOF to fail -// in its entirety if the only data written would be to the page containing the -// EOF. -TEST(MemfdTest, SealGrowPartialWriteTruncatedSamePage) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 3 / 4), SyscallSucceeds()); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds()); - - // EOF: 3/4 page, writing 1/2 page starting at 1/2 page would cause the file - // to grow. Since this would require only the page containing the EOF to be - // modified, the write is rejected entirely. - const std::vector<char> buf(kPageSize / 2); - EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2), - SyscallFailsWithErrno(EPERM)); - - // However, writing up to EOF is fine. - EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2), - SyscallSucceedsWithValue(kPageSize / 4)); -} - -// F_SEAL_SHRINK prevents a memfd from being shrunk using ftruncate. -TEST(MemfdTest, SealShrink) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK), - SyscallSucceeds()); - - // Shrink by half a page. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize / 2), - SyscallFailsWithErrno(EPERM)); - - // Ftruncate calls that don't actually shrink the file are allowed. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallSucceeds()); - ASSERT_THAT(ftruncate(memfd.get(), kPageSize * 2), SyscallSucceeds()); - - // After growing, shrinking is still not allowed. - ASSERT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM)); -} - -// F_SEAL_WRITE prevents a memfd from being written to through a write -// syscall. -TEST(MemfdTest, SealWriteWithWrite) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const std::vector<char> buf(kPageSize); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - - // Attemping to write at the end of the file fails. - EXPECT_THAT(write(memfd.get(), buf.data(), 1), SyscallFailsWithErrno(EPERM)); - - // Attemping to overwrite an existing part of the memfd fails. - EXPECT_THAT(pwrite(memfd.get(), buf.data(), 1, 0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size() / 2, kPageSize / 2), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(pwrite(memfd.get(), buf.data(), buf.size(), kPageSize / 2), - SyscallFailsWithErrno(EPERM)); - - // Zero-length writes however do not fail. - EXPECT_THAT(write(memfd.get(), buf.data(), 0), SyscallSucceeds()); -} - -// F_SEAL_WRITE prevents a memfd from being written to through an mmap. -TEST(MemfdTest, SealWriteWithMmap) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const std::vector<char> buf(kPageSize); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - - // Can't create a shared mapping with writes sealed. - void* ret = mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0); - EXPECT_EQ(ret, MAP_FAILED); - EXPECT_EQ(errno, EPERM); - ret = mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0); - EXPECT_EQ(ret, MAP_FAILED); - EXPECT_EQ(errno, EPERM); - - // However, private mappings are ok. - EXPECT_NO_ERRNO(Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - memfd.get(), 0)); -} - -// Adding F_SEAL_WRITE fails when there are outstanding writable mappings to a -// memfd. -TEST(MemfdTest, SealWriteWithOutstandingWritbleMapping) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const std::vector<char> buf(kPageSize); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Attempting to add F_SEAL_WRITE with active shared mapping with any set of - // permissions fails. - - // Read-only shared mapping. - { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, memfd.get(), 0)); - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), - SyscallFailsWithErrno(EBUSY)); - } - - // Write-only shared mapping. - { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), - SyscallFailsWithErrno(EBUSY)); - } - - // Read-write shared mapping. - { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - memfd.get(), 0)); - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), - SyscallFailsWithErrno(EBUSY)); - } - - // F_SEAL_WRITE can be set with private mappings with any permissions. - { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - memfd.get(), 0)); - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), - SyscallSucceeds()); - } -} - -// When applying F_SEAL_WRITE fails due to outstanding writable mappings, any -// additional seals passed to the same add seal call are also rejected. -TEST(MemfdTest, NoPartialSealApplicationWhenWriteSealRejected) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - - // Try add some seals along with F_SEAL_WRITE. The seal application should - // fail since there exists an active shared mapping. - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_GROW), - SyscallFailsWithErrno(EBUSY)); - - // None of the seals should be applied. - EXPECT_THAT(fcntl(memfd.get(), F_GET_SEALS), SyscallSucceedsWithValue(0)); -} - -// Seals are inode level properties, and apply to all file descriptors referring -// to a memfd. -TEST(MemfdTest, SealsAreInodeLevelProperties) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const FileDescriptor memfd2 = ASSERT_NO_ERRNO_AND_VALUE(memfd.Dup()); - - // Add seal through the original memfd, and verify that it appears on the - // dupped fd. - ASSERT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - EXPECT_THAT(fcntl(memfd2.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_WRITE)); - - // Verify the seal actually applies to both fds. - std::vector<char> buf(kPageSize); - EXPECT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(write(memfd2.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPERM)); - - // Seals are enforced on new FDs that are dupped after the seal is already - // applied. - const FileDescriptor memfd3 = ASSERT_NO_ERRNO_AND_VALUE(memfd2.Dup()); - EXPECT_THAT(write(memfd3.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPERM)); - - // Try a new seal applied to one of the dupped fds. - ASSERT_THAT(fcntl(memfd3.get(), F_ADD_SEALS, F_SEAL_GROW), SyscallSucceeds()); - EXPECT_THAT(ftruncate(memfd.get(), kPageSize), SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(ftruncate(memfd2.get(), kPageSize), SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(ftruncate(memfd3.get(), kPageSize), SyscallFailsWithErrno(EPERM)); -} - -// Tmpfs files also support seals, but are created with F_SEAL_SEAL. -TEST(MemfdTest, TmpfsFilesHaveSealSeal) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp"))); - const TempPath tmpfs_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn("/tmp")); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfs_file.path(), O_RDWR, 0644)); - EXPECT_THAT(fcntl(fd.get(), F_GET_SEALS), - SyscallSucceedsWithValue(F_SEAL_SEAL)); -} - -// Can open a memfd from procfs and use as normal. -TEST(MemfdTest, CanOpenFromProcfs) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - - // Write a random page of data to the memfd via write(2). - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Read back the same data from the fd obtained from procfs and verify. - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDWR)); - std::vector<char> buf2(kPageSize); - EXPECT_THAT(pread(fd.get(), buf2.data(), buf2.size(), 0), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(buf, buf2); -} - -// Test that memfd permissions are set up correctly to allow another process to -// open it from procfs. -TEST(MemfdTest, OtherProcessCanOpenFromProcfs) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - const auto memfd_path = - absl::StrFormat("/proc/%d/fd/%d", getpid(), memfd.get()); - const auto rest = [&] { - int fd = open(memfd_path.c_str(), O_RDWR); - TEST_PCHECK(fd >= 0); - TEST_PCHECK(close(fd) >= 0); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -// Test that only files opened as writable can have seals applied to them. -// Normally there's no way to specify file permissions on memfds, but we can -// obtain a read-only memfd by opening the corresponding procfs fd entry as -// read-only. -TEST(MemfdTest, MemfdMustBeWritableToModifySeals) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, MFD_ALLOW_SEALING)); - - // Initially adding a seal works. - EXPECT_THAT(fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_WRITE), SyscallSucceeds()); - - // Re-open the memfd as read-only from procfs. - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(absl::StrFormat("/proc/self/fd/%d", memfd.get()), O_RDONLY)); - - // Can't add seals through an unwritable fd. - EXPECT_THAT(fcntl(fd.get(), F_ADD_SEALS, F_SEAL_GROW), - SyscallFailsWithErrno(EPERM)); -} - -// Test that the memfd implementation internally tracks potentially writable -// maps correctly. -TEST(MemfdTest, MultipleWritableAndNonWritableRefsToSameFileRegion) { - const FileDescriptor memfd = - ASSERT_NO_ERRNO_AND_VALUE(MemfdCreate(kMemfdName, 0)); - - // Populate with a random page of data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(memfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Read-only map to the page. This should cause an initial mapping to be - // created. - Mapping m1 = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ, MAP_PRIVATE, memfd.get(), 0)); - - // Create a shared writable map to the page. This should cause the internal - // mapping to become potentially writable. - Mapping m2 = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, memfd.get(), 0)); - - // Drop the read-only mapping first. If writable-ness isn't tracked correctly, - // this can cause some misaccounting, which can trigger asserts internally. - m1.reset(); - m2.reset(); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/memory_accounting.cc b/test/syscalls/linux/memory_accounting.cc deleted file mode 100644 index 94aea4077..000000000 --- a/test/syscalls/linux/memory_accounting.cc +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/mman.h> - -#include <map> - -#include "gtest/gtest.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_split.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using ::absl::StrFormat; - -// AnonUsageFromMeminfo scrapes the current anonymous memory usage from -// /proc/meminfo and returns it in bytes. -PosixErrorOr<uint64_t> AnonUsageFromMeminfo() { - ASSIGN_OR_RETURN_ERRNO(auto meminfo, GetContents("/proc/meminfo")); - std::vector<std::string> lines(absl::StrSplit(meminfo, '\n')); - - // Try to find AnonPages line, the format is AnonPages:\\s+(\\d+) kB\n. - for (const auto& line : lines) { - if (!absl::StartsWith(line, "AnonPages:")) { - continue; - } - - std::vector<std::string> parts( - absl::StrSplit(line, ' ', absl::SkipEmpty())); - if (parts.size() == 3) { - // The size is the second field, let's try to parse it as a number. - ASSIGN_OR_RETURN_ERRNO(auto anon_kb, Atoi<uint64_t>(parts[1])); - return anon_kb * 1024; - } - - return PosixError(EINVAL, "AnonPages field in /proc/meminfo was malformed"); - } - - return PosixError(EINVAL, "AnonPages field not found in /proc/meminfo"); -} - -TEST(MemoryAccounting, AnonAccountingPreservedOnSaveRestore) { - // This test isn't meaningful on Linux. /proc/meminfo reports system-wide - // memory usage, which can change arbitrarily in Linux from other activity on - // the machine. In gvisor, this test is the only thing running on the - // "machine", so values in /proc/meminfo accurately reflect the memory used by - // the test. - SKIP_IF(!IsRunningOnGvisor()); - - uint64_t anon_initial = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo()); - - // Cause some anonymous memory usage. - uint64_t map_bytes = Megabytes(512); - char* mem = - static_cast<char*>(mmap(nullptr, map_bytes, PROT_READ | PROT_WRITE, - MAP_POPULATE | MAP_ANON | MAP_PRIVATE, -1, 0)); - ASSERT_NE(mem, MAP_FAILED) - << "Map failed, errno: " << errno << " (" << strerror(errno) << ")."; - - // Write something to each page to prevent them from being decommited on - // S/R. Zero pages are dropped on save. - for (uint64_t i = 0; i < map_bytes; i += kPageSize) { - mem[i] = 'a'; - } - - uint64_t anon_after_alloc = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo()); - EXPECT_THAT(anon_after_alloc, - EquivalentWithin(anon_initial + map_bytes, 0.03)); - - // We have many implicit S/R cycles from scraping /proc/meminfo throughout the - // test, but throw an explicit S/R in here as well. - MaybeSave(); - - // Usage should remain the same across S/R. - uint64_t anon_after_sr = ASSERT_NO_ERRNO_AND_VALUE(AnonUsageFromMeminfo()); - EXPECT_THAT(anon_after_sr, EquivalentWithin(anon_after_alloc, 0.03)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mempolicy.cc b/test/syscalls/linux/mempolicy.cc deleted file mode 100644 index 059fad598..000000000 --- a/test/syscalls/linux/mempolicy.cc +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sys/syscall.h> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "test/util/cleanup.h" -#include "test/util/memory_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#define BITS_PER_BYTE 8 - -#define MPOL_F_STATIC_NODES (1 << 15) -#define MPOL_F_RELATIVE_NODES (1 << 14) -#define MPOL_DEFAULT 0 -#define MPOL_PREFERRED 1 -#define MPOL_BIND 2 -#define MPOL_INTERLEAVE 3 -#define MPOL_LOCAL 4 -#define MPOL_F_NODE (1 << 0) -#define MPOL_F_ADDR (1 << 1) -#define MPOL_F_MEMS_ALLOWED (1 << 2) -#define MPOL_MF_STRICT (1 << 0) -#define MPOL_MF_MOVE (1 << 1) -#define MPOL_MF_MOVE_ALL (1 << 2) - -int get_mempolicy(int* policy, uint64_t* nmask, uint64_t maxnode, void* addr, - int flags) { - return syscall(SYS_get_mempolicy, policy, nmask, maxnode, addr, flags); -} - -int set_mempolicy(int mode, uint64_t* nmask, uint64_t maxnode) { - return syscall(SYS_set_mempolicy, mode, nmask, maxnode); -} - -int mbind(void* addr, unsigned long len, int mode, - const unsigned long* nodemask, unsigned long maxnode, - unsigned flags) { - return syscall(SYS_mbind, addr, len, mode, nodemask, maxnode, flags); -} - -// Creates a cleanup object that resets the calling thread's mempolicy to the -// system default when the calling scope ends. -Cleanup ScopedMempolicy() { - return Cleanup([] { - EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 0), SyscallSucceeds()); - }); -} - -// Temporarily change the memory policy for the calling thread within the -// caller's scope. -PosixErrorOr<Cleanup> ScopedSetMempolicy(int mode, uint64_t* nmask, - uint64_t maxnode) { - if (set_mempolicy(mode, nmask, maxnode)) { - return PosixError(errno, "set_mempolicy"); - } - return ScopedMempolicy(); -} - -TEST(MempolicyTest, CheckDefaultPolicy) { - int mode = 0; - uint64_t nodemask = 0; - ASSERT_THAT(get_mempolicy(&mode, &nodemask, sizeof(nodemask) * BITS_PER_BYTE, - nullptr, 0), - SyscallSucceeds()); - - EXPECT_EQ(MPOL_DEFAULT, mode); - EXPECT_EQ(0x0, nodemask); -} - -TEST(MempolicyTest, PolicyPreservedAfterSetMempolicy) { - uint64_t nodemask = 0x1; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy( - MPOL_BIND, &nodemask, sizeof(nodemask) * BITS_PER_BYTE)); - - int mode = 0; - uint64_t nodemask_after = 0x0; - ASSERT_THAT(get_mempolicy(&mode, &nodemask_after, - sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0), - SyscallSucceeds()); - EXPECT_EQ(MPOL_BIND, mode); - EXPECT_EQ(0x1, nodemask_after); - - // Try throw in some mode flags. - for (auto mode_flag : {MPOL_F_STATIC_NODES, MPOL_F_RELATIVE_NODES}) { - auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE( - ScopedSetMempolicy(MPOL_INTERLEAVE | mode_flag, &nodemask, - sizeof(nodemask) * BITS_PER_BYTE)); - mode = 0; - nodemask_after = 0x0; - ASSERT_THAT( - get_mempolicy(&mode, &nodemask_after, - sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0), - SyscallSucceeds()); - EXPECT_EQ(MPOL_INTERLEAVE | mode_flag, mode); - EXPECT_EQ(0x1, nodemask_after); - } -} - -TEST(MempolicyTest, SetMempolicyRejectsInvalidInputs) { - auto cleanup = ScopedMempolicy(); - uint64_t nodemask; - - if (IsRunningOnGvisor()) { - // Invalid nodemask, we only support a single node on gvisor. - nodemask = 0x4; - ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, - sizeof(nodemask) * BITS_PER_BYTE), - SyscallFailsWithErrno(EINVAL)); - } - - nodemask = 0x1; - - // Invalid mode. - ASSERT_THAT(set_mempolicy(7439, &nodemask, sizeof(nodemask) * BITS_PER_BYTE), - SyscallFailsWithErrno(EINVAL)); - - // Invalid nodemask size. - ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0), - SyscallFailsWithErrno(EINVAL)); - - // Invalid mode flag. - ASSERT_THAT( - set_mempolicy(MPOL_DEFAULT | MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES, - &nodemask, sizeof(nodemask) * BITS_PER_BYTE), - SyscallFailsWithErrno(EINVAL)); - - // MPOL_INTERLEAVE with empty nodemask. - nodemask = 0x0; - ASSERT_THAT(set_mempolicy(MPOL_INTERLEAVE, &nodemask, - sizeof(nodemask) * BITS_PER_BYTE), - SyscallFailsWithErrno(EINVAL)); -} - -// The manpages specify that the nodemask provided to set_mempolicy are -// considered empty if the nodemask pointer is null, or if the nodemask size is -// 0. We use a policy which accepts both empty and non-empty nodemasks -// (MPOL_PREFERRED), a policy which requires a non-empty nodemask (MPOL_BIND), -// and a policy which completely ignores the nodemask (MPOL_DEFAULT) to verify -// argument checking around nodemasks. -TEST(MempolicyTest, EmptyNodemaskOnSet) { - auto cleanup = ScopedMempolicy(); - - EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 1), SyscallSucceeds()); - EXPECT_THAT(set_mempolicy(MPOL_BIND, nullptr, 1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, nullptr, 1), SyscallSucceeds()); - - uint64_t nodemask = 0x1; - EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(set_mempolicy(MPOL_BIND, &nodemask, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, &nodemask, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(MempolicyTest, QueryAvailableNodes) { - uint64_t nodemask = 0; - ASSERT_THAT( - get_mempolicy(nullptr, &nodemask, sizeof(nodemask) * BITS_PER_BYTE, - nullptr, MPOL_F_MEMS_ALLOWED), - SyscallSucceeds()); - // We can only be sure there is a single node if running on gvisor. - if (IsRunningOnGvisor()) { - EXPECT_EQ(0x1, nodemask); - } - - // MPOL_F_ADDR and MPOL_F_NODE flags may not be combined with - // MPOL_F_MEMS_ALLLOWED. - for (auto flags : - {MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR, MPOL_F_MEMS_ALLOWED | MPOL_F_NODE, - MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR | MPOL_F_NODE}) { - ASSERT_THAT(get_mempolicy(nullptr, &nodemask, - sizeof(nodemask) * BITS_PER_BYTE, nullptr, flags), - SyscallFailsWithErrno(EINVAL)); - } -} - -TEST(MempolicyTest, GetMempolicyQueryNodeForAddress) { - uint64_t dummy_stack_address; - auto dummy_heap_address = absl::make_unique<uint64_t>(); - int mode; - - for (auto ptr : {&dummy_stack_address, dummy_heap_address.get()}) { - mode = -1; - ASSERT_THAT( - get_mempolicy(&mode, nullptr, 0, ptr, MPOL_F_ADDR | MPOL_F_NODE), - SyscallSucceeds()); - // If we're not running on gvisor, the address may be allocated on a - // different numa node. - if (IsRunningOnGvisor()) { - EXPECT_EQ(0, mode); - } - } - - void* invalid_address = reinterpret_cast<void*>(-1); - - // Invalid address. - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, invalid_address, - MPOL_F_ADDR | MPOL_F_NODE), - SyscallFailsWithErrno(EFAULT)); - - // Invalid mode pointer. - ASSERT_THAT(get_mempolicy(reinterpret_cast<int*>(invalid_address), nullptr, 0, - &dummy_stack_address, MPOL_F_ADDR | MPOL_F_NODE), - SyscallFailsWithErrno(EFAULT)); -} - -TEST(MempolicyTest, GetMempolicyCanOmitPointers) { - int mode; - uint64_t nodemask; - - // Omit nodemask pointer. - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, 0), SyscallSucceeds()); - // Omit mode pointer. - ASSERT_THAT(get_mempolicy(nullptr, &nodemask, - sizeof(nodemask) * BITS_PER_BYTE, nullptr, 0), - SyscallSucceeds()); - // Omit both pointers. - ASSERT_THAT(get_mempolicy(nullptr, nullptr, 0, nullptr, 0), - SyscallSucceeds()); -} - -TEST(MempolicyTest, GetMempolicyNextInterleaveNode) { - int mode; - // Policy for thread not yet set to MPOL_INTERLEAVE, can't query for - // the next node which will be used for allocation. - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE), - SyscallFailsWithErrno(EINVAL)); - - // Set default policy for thread to MPOL_INTERLEAVE. - uint64_t nodemask = 0x1; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy( - MPOL_INTERLEAVE, &nodemask, sizeof(nodemask) * BITS_PER_BYTE)); - - mode = -1; - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE), - SyscallSucceeds()); - EXPECT_EQ(0, mode); -} - -TEST(MempolicyTest, Mbind) { - // Temporarily set the thread policy to MPOL_PREFERRED. - const auto cleanup_thread_policy = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(MPOL_PREFERRED, nullptr, 0)); - - const auto mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS)); - - // vmas default to MPOL_DEFAULT irrespective of the thread policy (currently - // MPOL_PREFERRED). - int mode; - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR), - SyscallSucceeds()); - EXPECT_EQ(mode, MPOL_DEFAULT); - - // Set MPOL_PREFERRED for the vma and read it back. - ASSERT_THAT( - mbind(mapping.ptr(), mapping.len(), MPOL_PREFERRED, nullptr, 0, 0), - SyscallSucceeds()); - ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR), - SyscallSucceeds()); - EXPECT_EQ(mode, MPOL_PREFERRED); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mincore.cc b/test/syscalls/linux/mincore.cc deleted file mode 100644 index 5c1240c89..000000000 --- a/test/syscalls/linux/mincore.cc +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <stdint.h> -#include <string.h> -#include <sys/mman.h> -#include <unistd.h> - -#include <algorithm> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -size_t CountSetLSBs(std::vector<unsigned char> const& vec) { - return std::count_if(begin(vec), end(vec), - [](unsigned char c) { return (c & 1) != 0; }); -} - -TEST(MincoreTest, DirtyAnonPagesAreResident) { - constexpr size_t kTestPageCount = 10; - auto const kTestMappingBytes = kTestPageCount * kPageSize; - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - memset(m.ptr(), 0, m.len()); - - std::vector<unsigned char> vec(kTestPageCount, 0); - ASSERT_THAT(mincore(m.ptr(), kTestMappingBytes, vec.data()), - SyscallSucceeds()); - EXPECT_EQ(kTestPageCount, CountSetLSBs(vec)); -} - -TEST(MincoreTest, UnalignedAddressFails) { - // Map and touch two pages, then try to mincore the second half of the first - // page + the first half of the second page. Both pages are mapped, but - // mincore should return EINVAL due to the misaligned start address. - constexpr size_t kTestPageCount = 2; - auto const kTestMappingBytes = kTestPageCount * kPageSize; - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - memset(m.ptr(), 0, m.len()); - - std::vector<unsigned char> vec(kTestPageCount, 0); - EXPECT_THAT(mincore(reinterpret_cast<void*>(m.addr() + kPageSize / 2), - kPageSize, vec.data()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(MincoreTest, UnalignedLengthSucceedsAndIsRoundedUp) { - // Map and touch two pages, then try to mincore the first page + the first - // half of the second page. mincore should silently round up the length to - // include both pages. - constexpr size_t kTestPageCount = 2; - auto const kTestMappingBytes = kTestPageCount * kPageSize; - auto m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kTestMappingBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - memset(m.ptr(), 0, m.len()); - - std::vector<unsigned char> vec(kTestPageCount, 0); - ASSERT_THAT(mincore(m.ptr(), kPageSize + kPageSize / 2, vec.data()), - SyscallSucceeds()); - EXPECT_EQ(kTestPageCount, CountSetLSBs(vec)); -} - -TEST(MincoreTest, ZeroLengthSucceedsAndAllowsAnyVecBelowTaskSize) { - EXPECT_THAT(mincore(nullptr, 0, nullptr), SyscallSucceeds()); -} - -TEST(MincoreTest, InvalidLengthFails) { - EXPECT_THAT(mincore(nullptr, -1, nullptr), SyscallFailsWithErrno(ENOMEM)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mkdir.cc b/test/syscalls/linux/mkdir.cc deleted file mode 100644 index 11fbfa5c5..000000000 --- a/test/syscalls/linux/mkdir.cc +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class MkdirTest : public ::testing::Test { - protected: - // SetUp creates various configurations of files. - void SetUp() override { dirname_ = NewTempAbsPath(); } - - // TearDown unlinks created files. - void TearDown() override { - EXPECT_THAT(rmdir(dirname_.c_str()), SyscallSucceeds()); - } - - std::string dirname_; -}; - -TEST_F(MkdirTest, CanCreateWritableDir) { - ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); - std::string filename = JoinPath(dirname_, "anything"); - int fd; - ASSERT_THAT(fd = open(filename.c_str(), O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -TEST_F(MkdirTest, HonorsUmask) { - constexpr mode_t kMask = 0111; - TempUmask mask(kMask); - ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); - struct stat statbuf; - ASSERT_THAT(stat(dirname_.c_str(), &statbuf), SyscallSucceeds()); - EXPECT_EQ(0777 & ~kMask, statbuf.st_mode & 0777); -} - -TEST_F(MkdirTest, HonorsUmask2) { - constexpr mode_t kMask = 0142; - TempUmask mask(kMask); - ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); - struct stat statbuf; - ASSERT_THAT(stat(dirname_.c_str(), &statbuf), SyscallSucceeds()); - EXPECT_EQ(0777 & ~kMask, statbuf.st_mode & 0777); -} - -TEST_F(MkdirTest, FailsOnDirWithoutWritePerms) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - ASSERT_THAT(mkdir(dirname_.c_str(), 0555), SyscallSucceeds()); - auto dir = JoinPath(dirname_.c_str(), "foo"); - EXPECT_THAT(mkdir(dir.c_str(), 0777), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(open(JoinPath(dirname_, "file").c_str(), O_RDWR | O_CREAT, 0666), - SyscallFailsWithErrno(EACCES)); -} - -TEST_F(MkdirTest, DirAlreadyExists) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); - auto dir = JoinPath(dirname_.c_str(), "foo"); - EXPECT_THAT(mkdir(dir.c_str(), 0777), SyscallSucceeds()); - - struct { - int mode; - int err; - } tests[] = { - {.mode = 0000, .err = EACCES}, // No perm - {.mode = 0100, .err = EEXIST}, // Exec only - {.mode = 0200, .err = EACCES}, // Write only - {.mode = 0300, .err = EEXIST}, // Write+exec - {.mode = 0400, .err = EACCES}, // Read only - {.mode = 0500, .err = EEXIST}, // Read+exec - {.mode = 0600, .err = EACCES}, // Read+write - {.mode = 0700, .err = EEXIST}, // All - }; - for (const auto& t : tests) { - printf("mode: 0%o\n", t.mode); - EXPECT_THAT(chmod(dirname_.c_str(), t.mode), SyscallSucceeds()); - EXPECT_THAT(mkdir(dir.c_str(), 0777), SyscallFailsWithErrno(t.err)); - } - - // Clean up. - EXPECT_THAT(chmod(dirname_.c_str(), 0777), SyscallSucceeds()); - ASSERT_THAT(rmdir(dir.c_str()), SyscallSucceeds()); -} - -TEST_F(MkdirTest, MkdirAtEmptyPath) { - ASSERT_THAT(mkdir(dirname_.c_str(), 0777), SyscallSucceeds()); - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dirname_, O_RDONLY | O_DIRECTORY, 0666)); - EXPECT_THAT(mkdirat(fd.get(), "", 0777), SyscallFailsWithErrno(ENOENT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc deleted file mode 100644 index 1635c6d0c..000000000 --- a/test/syscalls/linux/mknod.cc +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/un.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(MknodTest, RegularFile) { - const std::string node0 = NewTempAbsPath(); - EXPECT_THAT(mknod(node0.c_str(), S_IFREG, 0), SyscallSucceeds()); - - const std::string node1 = NewTempAbsPath(); - EXPECT_THAT(mknod(node1.c_str(), 0, 0), SyscallSucceeds()); -} - -TEST(MknodTest, RegularFilePermissions) { - const std::string node = NewTempAbsPath(); - mode_t newUmask = 0077; - umask(newUmask); - - // Attempt to open file with mode 0777. Not specifying file type should create - // a regualar file. - mode_t perms = S_IRWXU | S_IRWXG | S_IRWXO; - EXPECT_THAT(mknod(node.c_str(), perms, 0), SyscallSucceeds()); - - // In the absence of a default ACL, the permissions of the created node are - // (mode & ~umask). -- mknod(2) - mode_t wantPerms = perms & ~newUmask; - struct stat st; - ASSERT_THAT(stat(node.c_str(), &st), SyscallSucceeds()); - ASSERT_EQ(st.st_mode & 0777, wantPerms); - - // "Zero file type is equivalent to type S_IFREG." - mknod(2) - ASSERT_EQ(st.st_mode & S_IFMT, S_IFREG); -} - -TEST(MknodTest, MknodAtFIFO) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string fifo_relpath = NewTempRelPath(); - const std::string fifo = JoinPath(dir.path(), fifo_relpath); - - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_RDONLY)); - ASSERT_THAT(mknodat(dirfd.get(), fifo_relpath.c_str(), S_IFIFO | S_IRUSR, 0), - SyscallSucceeds()); - - struct stat st; - ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISFIFO(st.st_mode)); -} - -TEST(MknodTest, MknodOnExistingPathFails) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath slink = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path())); - - EXPECT_THAT(mknod(file.path().c_str(), S_IFREG, 0), - SyscallFailsWithErrno(EEXIST)); - EXPECT_THAT(mknod(file.path().c_str(), S_IFIFO, 0), - SyscallFailsWithErrno(EEXIST)); - EXPECT_THAT(mknod(slink.path().c_str(), S_IFREG, 0), - SyscallFailsWithErrno(EEXIST)); - EXPECT_THAT(mknod(slink.path().c_str(), S_IFIFO, 0), - SyscallFailsWithErrno(EEXIST)); -} - -TEST(MknodTest, UnimplementedTypesReturnError) { - // TODO(gvisor.dev/issue/1624): These file types are supported by some - // filesystems in VFS2, so this test should be deleted along with VFS1. - SKIP_IF(!IsRunningWithVFS1()); - - const std::string path = NewTempAbsPath(); - EXPECT_THAT(mknod(path.c_str(), S_IFSOCK, 0), - SyscallFailsWithErrno(EOPNOTSUPP)); - EXPECT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM)); -} - -TEST(MknodTest, Socket) { - SKIP_IF(IsRunningOnGvisor() && IsRunningWithVFS1()); - - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - - auto filename = NewTempRelPath(); - - ASSERT_THAT(mknod(filename.c_str(), S_IFSOCK | S_IRUSR | S_IWUSR, 0), - SyscallSucceeds()); - - int sk; - ASSERT_THAT(sk = socket(AF_UNIX, SOCK_SEQPACKET, 0), SyscallSucceeds()); - FileDescriptor fd(sk); - - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - absl::SNPrintF(addr.sun_path, sizeof(addr.sun_path), "%s", filename.c_str()); - ASSERT_THAT(connect(sk, (struct sockaddr *)&addr, sizeof(addr)), - SyscallFailsWithErrno(ECONNREFUSED)); - ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds()); -} - -PosixErrorOr<FileDescriptor> OpenRetryEINTR(std::string const& path, int flags, - mode_t mode = 0) { - while (true) { - auto maybe_fd = Open(path, flags, mode); - if (maybe_fd.ok() || maybe_fd.error().errno_value() != EINTR) { - return maybe_fd; - } - } -} - -TEST(MknodTest, Fifo) { - const std::string fifo = NewTempAbsPath(); - ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0), - SyscallSucceeds()); - - struct stat st; - ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISFIFO(st.st_mode)); - - std::string msg = "some std::string"; - std::vector<char> buf(512); - - // Read-end of the pipe. - ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(msg.length())); - EXPECT_EQ(msg, std::string(buf.data())); - }); - - // Write-end of the pipe. - FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_WRONLY)); - EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), - SyscallSucceedsWithValue(msg.length())); -} - -TEST(MknodTest, FifoOtrunc) { - const std::string fifo = NewTempAbsPath(); - ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0), - SyscallSucceeds()); - - struct stat st = {}; - ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISFIFO(st.st_mode)); - - std::string msg = "some std::string"; - std::vector<char> buf(512); - // Read-end of the pipe. - ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(msg.length())); - EXPECT_EQ(msg, std::string(buf.data())); - }); - - // Write-end of the pipe. - FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE( - OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC)); - EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), - SyscallSucceedsWithValue(msg.length())); -} - -TEST(MknodTest, FifoTruncNoOp) { - const std::string fifo = NewTempAbsPath(); - ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0), - SyscallSucceeds()); - - EXPECT_THAT(truncate(fifo.c_str(), 0), SyscallFailsWithErrno(EINVAL)); - - struct stat st = {}; - ASSERT_THAT(stat(fifo.c_str(), &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISFIFO(st.st_mode)); - - std::string msg = "some std::string"; - std::vector<char> buf(512); - // Read-end of the pipe. - ScopedThread t([&fifo, &buf, &msg]() { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY)); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(msg.length())); - EXPECT_EQ(msg, std::string(buf.data())); - }); - - FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE( - OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC)); - EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()), - SyscallSucceedsWithValue(msg.length())); - EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(MknodTest, MknodAtEmptyPath) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY, 0666)); - EXPECT_THAT(mknodat(fd.get(), "", S_IFREG | 0777, 0), - SyscallFailsWithErrno(ENOENT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mlock.cc b/test/syscalls/linux/mlock.cc deleted file mode 100644 index 78ac96bed..000000000 --- a/test/syscalls/linux/mlock.cc +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/mman.h> -#include <sys/resource.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include <cerrno> -#include <cstring> - -#include "gmock/gmock.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/rlimit_util.h" -#include "test/util/test_util.h" - -using ::testing::_; - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<bool> CanMlock() { - struct rlimit rlim; - if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0) { - return PosixError(errno, "getrlimit(RLIMIT_MEMLOCK)"); - } - if (rlim.rlim_cur != 0) { - return true; - } - return HaveCapability(CAP_IPC_LOCK); -} - -// Returns true if the page containing addr is mlocked. -bool IsPageMlocked(uintptr_t addr) { - // This relies on msync(MS_INVALIDATE) interacting correctly with mlocked - // pages, which is tested for by the MsyncInvalidate case below. - int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)), - kPageSize, MS_ASYNC | MS_INVALIDATE); - if (rv == 0) { - return false; - } - // This uses TEST_PCHECK_MSG since it's used in subprocesses. - TEST_PCHECK_MSG(errno == EBUSY, "msync failed with unexpected errno"); - return true; -} - -TEST(MlockTest, Basic) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); -} - -TEST(MlockTest, ProtNone) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), - SyscallFailsWithErrno(ENOMEM)); - // ENOMEM is returned because mlock can't populate the page, but it's still - // considered locked. - EXPECT_TRUE(IsPageMlocked(mapping.addr())); -} - -TEST(MlockTest, MadviseDontneed) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_THAT(madvise(mapping.ptr(), mapping.len(), MADV_DONTNEED), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(MlockTest, MsyncInvalidate) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_ASYNC | MS_INVALIDATE), - SyscallFailsWithErrno(EBUSY)); - EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_SYNC | MS_INVALIDATE), - SyscallFailsWithErrno(EBUSY)); -} - -TEST(MlockTest, Fork) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - EXPECT_THAT( - InForkedProcess([&] { TEST_CHECK(!IsPageMlocked(mapping.addr())); }), - IsPosixErrorOkAndHolds(0)); -} - -TEST(MlockTest, RlimitMemlockZero) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), - SyscallFailsWithErrno(EPERM)); -} - -TEST(MlockTest, RlimitMemlockInsufficient) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize)); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), - SyscallFailsWithErrno(ENOMEM)); -} - -TEST(MunlockTest, Basic) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); -} - -TEST(MunlockTest, NotLocked) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); -} - -// There is currently no test for mlockall(MCL_CURRENT) because the default -// RLIMIT_MEMLOCK of 64 KB is insufficient to actually invoke -// mlockall(MCL_CURRENT). - -TEST(MlockallTest, Future) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - - // Run this test in a separate (single-threaded) subprocess to ensure that a - // background thread doesn't try to mmap a large amount of memory, fail due - // to hitting RLIMIT_MEMLOCK, and explode the process violently. - auto const do_test = [] { - auto const mapping = - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); - TEST_CHECK(!IsPageMlocked(mapping.addr())); - TEST_PCHECK(mlockall(MCL_FUTURE) == 0); - // Ensure that mlockall(MCL_FUTURE) is turned off before the end of the - // test, as otherwise mmaps may fail unexpectedly. - Cleanup do_munlockall([] { TEST_PCHECK(munlockall() == 0); }); - auto const mapping2 = - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); - TEST_CHECK(IsPageMlocked(mapping2.addr())); - // Fire munlockall() and check that it disables mlockall(MCL_FUTURE). - do_munlockall.Release()(); - auto const mapping3 = - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE).ValueOrDie(); - TEST_CHECK(!IsPageMlocked(mapping2.addr())); - }; - EXPECT_THAT(InForkedProcess(do_test), IsPosixErrorOkAndHolds(0)); -} - -TEST(MunlockallTest, Basic) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(munlockall(), SyscallSucceeds()); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); -} - -#ifndef SYS_mlock2 -#if defined(__x86_64__) -#define SYS_mlock2 325 -#elif defined(__aarch64__) -#define SYS_mlock2 284 -#endif -#endif - -#ifndef MLOCK_ONFAULT -#define MLOCK_ONFAULT 0x01 // Linux: include/uapi/asm-generic/mman-common.h -#endif - -#ifdef SYS_mlock2 - -int mlock2(void const* addr, size_t len, int flags) { - return syscall(SYS_mlock2, addr, len, flags); -} - -TEST(Mlock2Test, NoFlags) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), 0), SyscallSucceeds()); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); -} - -TEST(Mlock2Test, MlockOnfault) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); - ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), MLOCK_ONFAULT), - SyscallSucceeds()); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); -} - -TEST(Mlock2Test, UnknownFlags) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - EXPECT_THAT(mlock2(mapping.ptr(), mapping.len(), ~0), - SyscallFailsWithErrno(EINVAL)); -} - -#endif // defined(SYS_mlock2) - -TEST(MapLockedTest, Basic) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds()); - EXPECT_FALSE(IsPageMlocked(mapping.addr())); -} - -TEST(MapLockedTest, RlimitMemlockZero) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); - EXPECT_THAT( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED), - PosixErrorIs(EPERM, _)); -} - -TEST(MapLockedTest, RlimitMemlockInsufficient) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize)); - EXPECT_THAT( - MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED), - PosixErrorIs(EAGAIN, _)); -} - -TEST(MremapLockedTest, Basic) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - - void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), - MREMAP_MAYMOVE, nullptr); - if (addr == MAP_FAILED) { - FAIL() << "mremap failed: " << errno << " (" << strerror(errno) << ")"; - } - mapping.release(); - mapping.reset(addr, 2 * mapping.len()); - EXPECT_TRUE(IsPageMlocked(reinterpret_cast<uintptr_t>(addr))); -} - -TEST(MremapLockedTest, RlimitMemlockZero) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0)); - void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), - MREMAP_MAYMOVE, nullptr); - EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN) - << "addr = " << addr << ", errno = " << errno; -} - -TEST(MremapLockedTest, RlimitMemlockInsufficient) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock())); - auto mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED)); - EXPECT_TRUE(IsPageMlocked(mapping.addr())); - - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) { - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false)); - } - Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE( - ScopedSetSoftRlimit(RLIMIT_MEMLOCK, mapping.len())); - void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(), - MREMAP_MAYMOVE, nullptr); - EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN) - << "addr = " << addr << ", errno = " << errno; -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc deleted file mode 100644 index 93a6d9cde..000000000 --- a/test/syscalls/linux/mmap.cc +++ /dev/null @@ -1,1697 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/magic.h> -#include <linux/unistd.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/resource.h> -#include <sys/statfs.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/escaping.h" -#include "absl/strings/str_split.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::AnyOf; -using ::testing::Eq; -using ::testing::Gt; - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<int64_t> VirtualMemorySize() { - ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/statm")); - std::vector<std::string> parts = absl::StrSplit(contents, ' '); - if (parts.empty()) { - return PosixError(EINVAL, "Unable to parse /proc/self/statm"); - } - ASSIGN_OR_RETURN_ERRNO(auto pages, Atoi<int64_t>(parts[0])); - return pages * getpagesize(); -} - -class MMapTest : public ::testing::Test { - protected: - // Unmap mapping, if one was made. - void TearDown() override { - if (addr_) { - EXPECT_THAT(Unmap(), SyscallSucceeds()); - } - } - - // Remembers mapping, so it can be automatically unmapped. - uintptr_t Map(uintptr_t addr, size_t length, int prot, int flags, int fd, - off_t offset) { - void* ret = - mmap(reinterpret_cast<void*>(addr), length, prot, flags, fd, offset); - - if (ret != MAP_FAILED) { - addr_ = ret; - length_ = length; - } - - return reinterpret_cast<uintptr_t>(ret); - } - - // Unmap previous mapping - int Unmap() { - if (!addr_) { - return -1; - } - - int ret = munmap(addr_, length_); - - addr_ = nullptr; - length_ = 0; - - return ret; - } - - // Msync the mapping. - int Msync() { return msync(addr_, length_, MS_SYNC); } - - // Mlock the mapping. - int Mlock() { return mlock(addr_, length_); } - - // Munlock the mapping. - int Munlock() { return munlock(addr_, length_); } - - int Protect(uintptr_t addr, size_t length, int prot) { - return mprotect(reinterpret_cast<void*>(addr), length, prot); - } - - void* addr_ = nullptr; - size_t length_ = 0; -}; - -// Matches if arg contains the same contents as string str. -MATCHER_P(EqualsMemory, str, "") { - if (0 == memcmp(arg, str.c_str(), str.size())) { - return true; - } - - *result_listener << "Memory did not match. Got:\n" - << absl::BytesToHexString( - std::string(static_cast<char*>(arg), str.size())) - << "Want:\n" - << absl::BytesToHexString(str); - return false; -} - -// We can't map pipes, but for different reasons. -TEST_F(MMapTest, MapPipe) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[0], 0), - SyscallFailsWithErrno(ENODEV)); - EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fds[1], 0), - SyscallFailsWithErrno(EACCES)); - ASSERT_THAT(close(fds[0]), SyscallSucceeds()); - ASSERT_THAT(close(fds[1]), SyscallSucceeds()); -} - -// It's very common to mmap /dev/zero because anonymous mappings aren't part -// of POSIX although they are widely supported. So a zero initialized memory -// region would actually come from a "file backed" /dev/zero mapping. -TEST_F(MMapTest, MapDevZeroShared) { - // This test will verify that we're able to map a page backed by /dev/zero - // as MAP_SHARED. - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - // Test that we can create a RW SHARED mapping of /dev/zero. - ASSERT_THAT( - Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), - SyscallSucceeds()); -} - -TEST_F(MMapTest, MapDevZeroPrivate) { - // This test will verify that we're able to map a page backed by /dev/zero - // as MAP_PRIVATE. - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - // Test that we can create a RW SHARED mapping of /dev/zero. - ASSERT_THAT( - Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0), - SyscallSucceeds()); -} - -TEST_F(MMapTest, MapDevZeroNoPersistence) { - // This test will verify that two independent mappings of /dev/zero do not - // appear to reference the same "backed file." - - const FileDescriptor dev_zero1 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - const FileDescriptor dev_zero2 = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - ASSERT_THAT( - Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero1.get(), 0), - SyscallSucceeds()); - - // Create a second mapping via the second /dev/zero fd. - void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - dev_zero2.get(), 0); - ASSERT_THAT(reinterpret_cast<intptr_t>(psec_map), SyscallSucceeds()); - - // Always unmap. - auto cleanup_psec_map = Cleanup( - [&] { EXPECT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); }); - - // Verify that we have independently addressed pages. - ASSERT_NE(psec_map, addr_); - - std::string buf_zero(kPageSize, 0x00); - std::string buf_ones(kPageSize, 0xFF); - - // Verify the first is actually all zeros after mmap. - EXPECT_THAT(addr_, EqualsMemory(buf_zero)); - - // Let's fill in the first mapping with 0xFF. - memcpy(addr_, buf_ones.data(), kPageSize); - - // Verify that the memcpy actually stuck in the page. - EXPECT_THAT(addr_, EqualsMemory(buf_ones)); - - // Verify that it didn't affect the second page which should be all zeros. - EXPECT_THAT(psec_map, EqualsMemory(buf_zero)); -} - -TEST_F(MMapTest, MapDevZeroSharedMultiplePages) { - // This will test that we're able to map /dev/zero over multiple pages. - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - // Test that we can create a RW SHARED mapping of /dev/zero. - ASSERT_THAT(Map(0, kPageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE, - dev_zero.get(), 0), - SyscallSucceeds()); - - std::string buf_zero(kPageSize * 2, 0x00); - std::string buf_ones(kPageSize * 2, 0xFF); - - // Verify the two pages are actually all zeros after mmap. - EXPECT_THAT(addr_, EqualsMemory(buf_zero)); - - // Fill out the pages with all ones. - memcpy(addr_, buf_ones.data(), kPageSize * 2); - - // Verify that the memcpy actually stuck in the pages. - EXPECT_THAT(addr_, EqualsMemory(buf_ones)); -} - -TEST_F(MMapTest, MapDevZeroSharedFdNoPersistence) { - // This test will verify that two independent mappings of /dev/zero do not - // appear to reference the same "backed file" even when mapped from the - // same initial fd. - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - ASSERT_THAT( - Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), - SyscallSucceeds()); - - // Create a second mapping via the same fd. - void* psec_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - dev_zero.get(), 0); - ASSERT_THAT(reinterpret_cast<int64_t>(psec_map), SyscallSucceeds()); - - // Always unmap. - auto cleanup_psec_map = Cleanup( - [&] { ASSERT_THAT(munmap(psec_map, kPageSize), SyscallSucceeds()); }); - - // Verify that we have independently addressed pages. - ASSERT_NE(psec_map, addr_); - - std::string buf_zero(kPageSize, 0x00); - std::string buf_ones(kPageSize, 0xFF); - - // Verify the first is actually all zeros after mmap. - EXPECT_THAT(addr_, EqualsMemory(buf_zero)); - - // Let's fill in the first mapping with 0xFF. - memcpy(addr_, buf_ones.data(), kPageSize); - - // Verify that the memcpy actually stuck in the page. - EXPECT_THAT(addr_, EqualsMemory(buf_ones)); - - // Verify that it didn't affect the second page which should be all zeros. - EXPECT_THAT(psec_map, EqualsMemory(buf_zero)); -} - -TEST_F(MMapTest, MapDevZeroSegfaultAfterUnmap) { - SetupGvisorDeathTest(); - - // This test will verify that we're able to map a page backed by /dev/zero - // as MAP_SHARED and after it's unmapped any access results in a SIGSEGV. - // This test is redundant but given the special nature of /dev/zero mappings - // it doesn't hurt. - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - - const auto rest = [&] { - // Test that we can create a RW SHARED mapping of /dev/zero. - TEST_PCHECK(Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - dev_zero.get(), - 0) != reinterpret_cast<uintptr_t>(MAP_FAILED)); - - // Confirm that accesses after the unmap result in a SIGSEGV. - // - // N.B. We depend on this process being single-threaded to ensure there - // can't be another mmap to map addr before the dereference below. - void* addr_saved = addr_; // Unmap resets addr_. - TEST_PCHECK(Unmap() == 0); - *reinterpret_cast<volatile int*>(addr_saved) = 0xFF; - }; - - EXPECT_THAT(InForkedProcess(rest), - IsPosixErrorOkAndHolds(AnyOf(Eq(W_EXITCODE(0, SIGSEGV)), - Eq(W_EXITCODE(0, 128 + SIGSEGV))))); -} - -TEST_F(MMapTest, MapDevZeroUnaligned) { - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDWR)); - const size_t size = kPageSize + kPageSize / 2; - const std::string buf_zero(size, 0x00); - - ASSERT_THAT( - Map(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dev_zero.get(), 0), - SyscallSucceeds()); - EXPECT_THAT(addr_, EqualsMemory(buf_zero)); - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - ASSERT_THAT( - Map(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, dev_zero.get(), 0), - SyscallSucceeds()); - EXPECT_THAT(addr_, EqualsMemory(buf_zero)); -} - -// We can't map _some_ character devices. -TEST_F(MMapTest, MapCharDevice) { - const FileDescriptor cdevfd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/random", 0, 0)); - EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, cdevfd.get(), 0), - SyscallFailsWithErrno(ENODEV)); -} - -// We can't map directories. -TEST_F(MMapTest, MapDirectory) { - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), 0, 0)); - EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_PRIVATE, dirfd.get(), 0), - SyscallFailsWithErrno(ENODEV)); -} - -// We can map *something* -TEST_F(MMapTest, MapAnything) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceedsWithValue(Gt(0))); -} - -// Map length < PageSize allowed -TEST_F(MMapTest, SmallMap) { - EXPECT_THAT(Map(0, 128, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); -} - -// Hint address doesn't break anything. -// Note: there is no requirement we actually get the hint address -TEST_F(MMapTest, HintAddress) { - EXPECT_THAT( - Map(0x30000000, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); -} - -// MAP_FIXED gives us exactly the requested address -TEST_F(MMapTest, MapFixed) { - EXPECT_THAT(Map(0x30000000, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0), - SyscallSucceedsWithValue(0x30000000)); -} - -// 64-bit addresses work too -#if defined(__x86_64__) || defined(__aarch64__) -TEST_F(MMapTest, MapFixed64) { - EXPECT_THAT(Map(0x300000000000, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0), - SyscallSucceedsWithValue(0x300000000000)); -} -#endif - -// MAP_STACK allowed. -// There isn't a good way to verify it did anything. -TEST_F(MMapTest, MapStack) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0), - SyscallSucceeds()); -} - -// MAP_LOCKED allowed. -// There isn't a good way to verify it did anything. -TEST_F(MMapTest, MapLocked) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0), - SyscallSucceeds()); -} - -// MAP_PRIVATE or MAP_SHARED must be passed -TEST_F(MMapTest, NotPrivateOrShared) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Only one of MAP_PRIVATE or MAP_SHARED may be passed -TEST_F(MMapTest, PrivateAndShared) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_SHARED | MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(MMapTest, FixedAlignment) { - // Addr must be page aligned (MAP_FIXED) - EXPECT_THAT(Map(0x30000001, kPageSize, PROT_NONE, - MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Non-MAP_FIXED address does not need to be page aligned -TEST_F(MMapTest, NonFixedAlignment) { - EXPECT_THAT( - Map(0x30000001, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); -} - -// Length = 0 results in EINVAL. -TEST_F(MMapTest, InvalidLength) { - EXPECT_THAT(Map(0, 0, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Bad fd not allowed. -TEST_F(MMapTest, BadFd) { - EXPECT_THAT(Map(0, kPageSize, PROT_NONE, MAP_PRIVATE, 999, 0), - SyscallFailsWithErrno(EBADF)); -} - -// Mappings are writable. -TEST_F(MMapTest, ProtWrite) { - uint64_t addr; - constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; - - EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - // This shouldn't cause a SIGSEGV. - memset(reinterpret_cast<void*>(addr), 42, kPageSize); - - // The written data should actually be there. - EXPECT_EQ( - 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord))); -} - -// "Write-only" mappings are writable *and* readable. -TEST_F(MMapTest, ProtWriteOnly) { - uint64_t addr; - constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; - - EXPECT_THAT( - addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - // This shouldn't cause a SIGSEGV. - memset(reinterpret_cast<void*>(addr), 42, kPageSize); - - // The written data should actually be there. - EXPECT_EQ( - 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord))); -} - -// "Write-only" mappings are readable. -// -// This is distinct from above to ensure the page is accessible even if the -// initial fault is a write fault. -TEST_F(MMapTest, ProtWriteOnlyReadable) { - uint64_t addr; - constexpr uint64_t kFirstWord = 0; - - EXPECT_THAT( - addr = Map(0, kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), &kFirstWord, - sizeof(kFirstWord))); -} - -// Mappings are writable after mprotect from PROT_NONE to PROT_READ|PROT_WRITE. -TEST_F(MMapTest, ProtectProtWrite) { - uint64_t addr; - constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; - - EXPECT_THAT( - addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE), - SyscallSucceeds()); - - // This shouldn't cause a SIGSEGV. - memset(reinterpret_cast<void*>(addr), 42, kPageSize); - - // The written data should actually be there. - EXPECT_EQ( - 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord))); -} - -// SIGSEGV raised when reading PROT_NONE memory -TEST_F(MMapTest, ProtNoneDeath) { - SetupGvisorDeathTest(); - - uintptr_t addr; - - ASSERT_THAT( - addr = Map(0, kPageSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - EXPECT_EXIT(*reinterpret_cast<volatile int*>(addr), - ::testing::KilledBySignal(SIGSEGV), ""); -} - -// SIGSEGV raised when writing PROT_READ only memory -TEST_F(MMapTest, ReadOnlyDeath) { - SetupGvisorDeathTest(); - - uintptr_t addr; - - ASSERT_THAT( - addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - EXPECT_EXIT(*reinterpret_cast<volatile int*>(addr) = 42, - ::testing::KilledBySignal(SIGSEGV), ""); -} - -// Writable mapping mprotect'd to read-only should not be writable. -TEST_F(MMapTest, MprotectReadOnlyDeath) { - SetupGvisorDeathTest(); - - uintptr_t addr; - - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - volatile int* val = reinterpret_cast<int*>(addr); - - // Copy to ensure page is mapped in. - *val = 42; - - ASSERT_THAT(Protect(addr, kPageSize, PROT_READ), SyscallSucceeds()); - - // Now it shouldn't be writable. - EXPECT_EXIT(*val = 0, ::testing::KilledBySignal(SIGSEGV), ""); -} - -// Verify that calling mprotect an address that's not page aligned fails. -TEST_F(MMapTest, MprotectNotPageAligned) { - uintptr_t addr; - - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - ASSERT_THAT(Protect(addr + 1, kPageSize - 1, PROT_READ), - SyscallFailsWithErrno(EINVAL)); -} - -// Verify that calling mprotect with an absurdly huge length fails. -TEST_F(MMapTest, MprotectHugeLength) { - uintptr_t addr; - - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - ASSERT_THAT(Protect(addr, static_cast<size_t>(-1), PROT_READ), - SyscallFailsWithErrno(ENOMEM)); -} - -#if defined(__x86_64__) || defined(__i386__) -// This code is equivalent in 32 and 64-bit mode -const uint8_t machine_code[] = { - 0xb8, 0x2a, 0x00, 0x00, 0x00, // movl $42, %eax - 0xc3, // retq -}; -#elif defined(__aarch64__) -const uint8_t machine_code[] = { - 0x40, 0x05, 0x80, 0x52, // mov w0, #42 - 0xc0, 0x03, 0x5f, 0xd6, // ret -}; -#endif - -// PROT_EXEC allows code execution -TEST_F(MMapTest, ProtExec) { - uintptr_t addr; - uint32_t (*func)(void); - - EXPECT_THAT(addr = Map(0, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code)); - -#if defined(__aarch64__) - // We use this as a memory barrier for Arm64. - ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_EXEC), - SyscallSucceeds()); -#endif - - func = reinterpret_cast<uint32_t (*)(void)>(addr); - - EXPECT_EQ(42, func()); -} - -// No PROT_EXEC disallows code execution -TEST_F(MMapTest, NoProtExecDeath) { - SetupGvisorDeathTest(); - - uintptr_t addr; - uint32_t (*func)(void); - - EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - - memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code)); - - func = reinterpret_cast<uint32_t (*)(void)>(addr); - - EXPECT_EXIT(func(), ::testing::KilledBySignal(SIGSEGV), ""); -} - -TEST_F(MMapTest, NoExceedLimitData) { - void* prevbrk; - void* target_brk; - struct rlimit setlim; - - prevbrk = sbrk(0); - ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk)); - target_brk = reinterpret_cast<char*>(prevbrk) + 1; - - setlim.rlim_cur = RLIM_INFINITY; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); - EXPECT_THAT(brk(target_brk), SyscallSucceedsWithValue(0)); -} - -TEST_F(MMapTest, ExceedLimitData) { - // To unit test this more precisely, we'd need access to the mm's start_brk - // and end_brk, which we don't have direct access to :/ - void* prevbrk; - void* target_brk; - struct rlimit setlim; - - prevbrk = sbrk(0); - ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk)); - target_brk = reinterpret_cast<char*>(prevbrk) + 8192; - - setlim.rlim_cur = 0; - setlim.rlim_max = RLIM_INFINITY; - // Set RLIMIT_DATA very low so any subsequent brk() calls fail. - // Reset RLIMIT_DATA during teardown step. - ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); - EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); - // Teardown step... - setlim.rlim_cur = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); -} - -TEST_F(MMapTest, ExceedLimitDataPrlimit) { - // To unit test this more precisely, we'd need access to the mm's start_brk - // and end_brk, which we don't have direct access to :/ - void* prevbrk; - void* target_brk; - struct rlimit setlim; - - prevbrk = sbrk(0); - ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk)); - target_brk = reinterpret_cast<char*>(prevbrk) + 8192; - - setlim.rlim_cur = 0; - setlim.rlim_max = RLIM_INFINITY; - // Set RLIMIT_DATA very low so any subsequent brk() calls fail. - // Reset RLIMIT_DATA during teardown step. - ASSERT_THAT(prlimit(0, RLIMIT_DATA, &setlim, nullptr), SyscallSucceeds()); - EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); - // Teardown step... - setlim.rlim_cur = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); -} - -TEST_F(MMapTest, ExceedLimitDataPrlimitPID) { - // To unit test this more precisely, we'd need access to the mm's start_brk - // and end_brk, which we don't have direct access to :/ - void* prevbrk; - void* target_brk; - struct rlimit setlim; - - prevbrk = sbrk(0); - ASSERT_NE(-1, reinterpret_cast<intptr_t>(prevbrk)); - target_brk = reinterpret_cast<char*>(prevbrk) + 8192; - - setlim.rlim_cur = 0; - setlim.rlim_max = RLIM_INFINITY; - // Set RLIMIT_DATA very low so any subsequent brk() calls fail. - // Reset RLIMIT_DATA during teardown step. - ASSERT_THAT(prlimit(syscall(__NR_gettid), RLIMIT_DATA, &setlim, nullptr), - SyscallSucceeds()); - EXPECT_THAT(brk(target_brk), SyscallFailsWithErrno(ENOMEM)); - // Teardown step... - setlim.rlim_cur = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_DATA, &setlim), SyscallSucceeds()); -} - -TEST_F(MMapTest, NoExceedLimitAS) { - constexpr uint64_t kAllocBytes = 200 << 20; - // Add some headroom to the AS limit in case of e.g. unexpected stack - // expansion. - constexpr uint64_t kExtraASBytes = kAllocBytes + (20 << 20); - static_assert(kAllocBytes < kExtraASBytes, - "test depends on allocation not exceeding AS limit"); - - auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize()); - struct rlimit setlim; - setlim.rlim_cur = vss + kExtraASBytes; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds()); - EXPECT_THAT( - Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceedsWithValue(Gt(0))); -} - -TEST_F(MMapTest, ExceedLimitAS) { - constexpr uint64_t kAllocBytes = 200 << 20; - // Add some headroom to the AS limit in case of e.g. unexpected stack - // expansion. - constexpr uint64_t kExtraASBytes = 20 << 20; - static_assert(kAllocBytes > kExtraASBytes, - "test depends on allocation exceeding AS limit"); - - auto vss = ASSERT_NO_ERRNO_AND_VALUE(VirtualMemorySize()); - struct rlimit setlim; - setlim.rlim_cur = vss + kExtraASBytes; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_AS, &setlim), SyscallSucceeds()); - EXPECT_THAT( - Map(0, kAllocBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(ENOMEM)); -} - -// Tests that setting an anonymous mmap to PROT_NONE doesn't free the memory. -TEST_F(MMapTest, SettingProtNoneDoesntFreeMemory) { - uintptr_t addr; - constexpr uint8_t kFirstWord[] = {42, 42, 42, 42}; - - EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceedsWithValue(Gt(0))); - - memset(reinterpret_cast<void*>(addr), 42, kPageSize); - - ASSERT_THAT(Protect(addr, kPageSize, PROT_NONE), SyscallSucceeds()); - ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_WRITE), - SyscallSucceeds()); - - // The written data should still be there. - EXPECT_EQ( - 0, memcmp(reinterpret_cast<void*>(addr), kFirstWord, sizeof(kFirstWord))); -} - -constexpr char kFileContents[] = "Hello World!"; - -class MMapFileTest : public MMapTest { - protected: - FileDescriptor fd_; - std::string filename_; - - // Open a file for read/write - void SetUp() override { - MMapTest::SetUp(); - - filename_ = NewTempAbsPath(); - fd_ = ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_CREAT | O_RDWR, 0644)); - - // Extend file so it can be written once mapped. Deliberately make the file - // only half a page in size, so we can test what happens when we access the - // second half. - // Use ftruncate(2) once the sentry supports it. - char zero = 0; - size_t count = 0; - do { - const DisableSave ds; // saving 2048 times is slow and useless. - Write(&zero, 1), SyscallSucceedsWithValue(1); - } while (++count < (kPageSize / 2)); - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - } - - // Close and delete file - void TearDown() override { - MMapTest::TearDown(); - fd_.reset(); // Make sure the files is closed before we unlink it. - ASSERT_THAT(unlink(filename_.c_str()), SyscallSucceeds()); - } - - ssize_t Read(char* buf, size_t count) { - ssize_t len = 0; - do { - ssize_t ret = read(fd_.get(), buf, count); - if (ret < 0) { - return ret; - } else if (ret == 0) { - return len; - } - - len += ret; - buf += ret; - } while (len < static_cast<ssize_t>(count)); - - return len; - } - - ssize_t Write(const char* buf, size_t count) { - ssize_t len = 0; - do { - ssize_t ret = write(fd_.get(), buf, count); - if (ret < 0) { - return ret; - } else if (ret == 0) { - return len; - } - - len += ret; - buf += ret; - } while (len < static_cast<ssize_t>(count)); - - return len; - } -}; - -class MMapFileParamTest - : public MMapFileTest, - public ::testing::WithParamInterface<std::tuple<int, int>> { - protected: - int prot() const { return std::get<0>(GetParam()); } - - int flags() const { return std::get<1>(GetParam()); } -}; - -// MAP_POPULATE allowed. -// There isn't a good way to verify it actually did anything. -TEST_P(MMapFileParamTest, MapPopulate) { - ASSERT_THAT(Map(0, kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), - SyscallSucceeds()); -} - -// MAP_POPULATE on a short file. -TEST_P(MMapFileParamTest, MapPopulateShort) { - ASSERT_THAT( - Map(0, 2 * kPageSize, prot(), flags() | MAP_POPULATE, fd_.get(), 0), - SyscallSucceeds()); -} - -// Read contents from mapped file. -TEST_F(MMapFileTest, Read) { - size_t len = strlen(kFileContents); - ASSERT_EQ(len, Write(kFileContents, len)); - - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), 0), - SyscallSucceeds()); - - EXPECT_THAT(reinterpret_cast<char*>(addr), - EqualsMemory(std::string(kFileContents))); -} - -// Map at an offset. -TEST_F(MMapFileTest, MapOffset) { - ASSERT_THAT(lseek(fd_.get(), kPageSize, SEEK_SET), SyscallSucceeds()); - - size_t len = strlen(kFileContents); - ASSERT_EQ(len, Write(kFileContents, len)); - - uintptr_t addr; - ASSERT_THAT( - addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd_.get(), kPageSize), - SyscallSucceeds()); - - EXPECT_THAT(reinterpret_cast<char*>(addr), - EqualsMemory(std::string(kFileContents))); -} - -TEST_F(MMapFileTest, MapOffsetBeyondEnd) { - SetupGvisorDeathTest(); - - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - fd_.get(), 10 * kPageSize), - SyscallSucceeds()); - - // Touching the memory causes SIGBUS. - size_t len = strlen(kFileContents); - EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, - reinterpret_cast<volatile char*>(addr)), - ::testing::KilledBySignal(SIGBUS), ""); -} - -// Verify mmap fails when sum of length and offset overflows. -TEST_F(MMapFileTest, MapLengthPlusOffsetOverflows) { - const size_t length = static_cast<size_t>(-kPageSize); - const off_t offset = kPageSize; - ASSERT_THAT(Map(0, length, PROT_READ, MAP_PRIVATE, fd_.get(), offset), - SyscallFailsWithErrno(ENOMEM)); -} - -// MAP_PRIVATE PROT_WRITE is allowed on read-only FDs. -TEST_F(MMapFileTest, WritePrivateOnReadOnlyFd) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); - - uintptr_t addr; - EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - fd.get(), 0), - SyscallSucceeds()); - - // Touch the page to ensure the kernel didn't lie about writability. - size_t len = strlen(kFileContents); - std::copy(kFileContents, kFileContents + len, - reinterpret_cast<volatile char*>(addr)); -} - -// MAP_SHARED PROT_WRITE not allowed on read-only FDs. -TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_RDONLY)); - - uintptr_t addr; - EXPECT_THAT( - addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0), - SyscallFailsWithErrno(EACCES)); -} - -// Mmap not allowed on O_PATH FDs. -TEST_F(MMapFileTest, MmapFileWithOpath) { - 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_PATH)); - - uintptr_t addr; - EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0), - SyscallFailsWithErrno(EBADF)); -} - -// The FD must be readable. -TEST_P(MMapFileParamTest, WriteOnlyFd) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename_, O_WRONLY)); - - uintptr_t addr; - EXPECT_THAT(addr = Map(0, kPageSize, prot(), flags(), fd.get(), 0), - SyscallFailsWithErrno(EACCES)); -} - -// Overwriting the contents of a file mapped MAP_SHARED PROT_READ -// should cause the new data to be reflected in the mapping. -TEST_F(MMapFileTest, ReadSharedConsistentWithOverwrite) { - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Expand the file to two pages and dirty them. - std::string bufA(kPageSize, 'a'); - ASSERT_THAT(Write(bufA.c_str(), bufA.size()), - SyscallSucceedsWithValue(bufA.size())); - std::string bufB(kPageSize, 'b'); - ASSERT_THAT(Write(bufB.c_str(), bufB.size()), - SyscallSucceedsWithValue(bufB.size())); - - // Map the page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Check that the mapping contains the right file data. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufA.c_str(), kPageSize)); - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufB.c_str(), - kPageSize)); - - // Start at the beginning of the file. - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Swap the write pattern. - ASSERT_THAT(Write(bufB.c_str(), bufB.size()), - SyscallSucceedsWithValue(bufB.size())); - ASSERT_THAT(Write(bufA.c_str(), bufA.size()), - SyscallSucceedsWithValue(bufA.size())); - - // Check that the mapping got updated. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufB.c_str(), kPageSize)); - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufA.c_str(), - kPageSize)); -} - -// Partially overwriting a file mapped MAP_SHARED PROT_READ should be reflected -// in the mapping. -TEST_F(MMapFileTest, ReadSharedConsistentWithPartialOverwrite) { - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Expand the file to two pages and dirty them. - std::string bufA(kPageSize, 'a'); - ASSERT_THAT(Write(bufA.c_str(), bufA.size()), - SyscallSucceedsWithValue(bufA.size())); - std::string bufB(kPageSize, 'b'); - ASSERT_THAT(Write(bufB.c_str(), bufB.size()), - SyscallSucceedsWithValue(bufB.size())); - - // Map the page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Check that the mapping contains the right file data. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufA.c_str(), kPageSize)); - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize), bufB.c_str(), - kPageSize)); - - // Start at the beginning of the file. - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Do a partial overwrite, spanning both pages. - std::string bufC(kPageSize + (kPageSize / 2), 'c'); - ASSERT_THAT(Write(bufC.c_str(), bufC.size()), - SyscallSucceedsWithValue(bufC.size())); - - // Check that the mapping got updated. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), bufC.c_str(), - kPageSize + (kPageSize / 2))); - EXPECT_EQ(0, - memcmp(reinterpret_cast<void*>(addr + kPageSize + (kPageSize / 2)), - bufB.c_str(), kPageSize / 2)); -} - -// Overwriting a file mapped MAP_SHARED PROT_READ should be reflected in the -// mapping and the file. -TEST_F(MMapFileTest, ReadSharedConsistentWithWriteAndFile) { - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Expand the file to two full pages and dirty it. - std::string bufA(2 * kPageSize, 'a'); - ASSERT_THAT(Write(bufA.c_str(), bufA.size()), - SyscallSucceedsWithValue(bufA.size())); - - // Map only the first page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Prepare to overwrite the file contents. - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Overwrite everything, beyond the mapped portion. - std::string bufB(2 * kPageSize, 'b'); - ASSERT_THAT(Write(bufB.c_str(), bufB.size()), - SyscallSucceedsWithValue(bufB.size())); - - // What the mapped portion should now look like. - std::string bufMapped(kPageSize, 'b'); - - // Expect that the mapped portion is consistent. - EXPECT_EQ( - 0, memcmp(reinterpret_cast<void*>(addr), bufMapped.c_str(), kPageSize)); - - // Prepare to read the entire file contents. - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Expect that the file was fully updated. - std::vector<char> bufFile(2 * kPageSize); - ASSERT_THAT(Read(bufFile.data(), bufFile.size()), - SyscallSucceedsWithValue(bufFile.size())); - // Cast to void* to avoid EXPECT_THAT assuming bufFile.data() is a - // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C - // std::string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(bufFile.data()), EqualsMemory(bufB)); -} - -// Write data to mapped file. -TEST_F(MMapFileTest, WriteShared) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - size_t len = strlen(kFileContents); - memcpy(reinterpret_cast<void*>(addr), kFileContents, len); - - // The file may not actually be updated until munmap is called. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - std::vector<char> buf(len); - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a - // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C - // string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(buf.data()), - EqualsMemory(std::string(kFileContents))); -} - -// Write data to portion of mapped page beyond the end of the file. -// These writes are not reflected in the file. -TEST_F(MMapFileTest, WriteSharedBeyondEnd) { - // The file is only half of a page. We map an entire page. Writes to the - // end of the mapping must not be reflected in the file. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - // First half; this is reflected in the file. - std::string first(kPageSize / 2, 'A'); - memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size()); - - // Second half; this is not reflected in the file. - std::string second(kPageSize / 2, 'B'); - memcpy(reinterpret_cast<void*>(addr + kPageSize / 2), second.c_str(), - second.size()); - - // The file may not actually be updated until munmap is called. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - // Big enough to fit the entire page, if the writes are mistakenly written to - // the file. - std::vector<char> buf(kPageSize); - - // Only the first half is in the file. - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(first.size())); - // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a - // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C - // NUL-terminated C std::string. EXPECT_THAT will try to print a char* as a C - // std::string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(first)); -} - -// The portion of a mapped page that becomes part of the file after a truncate -// is reflected in the file. -TEST_F(MMapFileTest, WriteSharedTruncateUp) { - // The file is only half of a page. We map an entire page. Writes to the - // end of the mapping must not be reflected in the file. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - // First half; this is reflected in the file. - std::string first(kPageSize / 2, 'A'); - memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size()); - - // Second half; this is not reflected in the file now (see - // WriteSharedBeyondEnd), but will be after the truncate. - std::string second(kPageSize / 2, 'B'); - memcpy(reinterpret_cast<void*>(addr + kPageSize / 2), second.c_str(), - second.size()); - - // Extend the file to a full page. The second half of the page will be - // reflected in the file. - EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); - - // The file may not actually be updated until munmap is called. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - // The whole page is in the file. - std::vector<char> buf(kPageSize); - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a - // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C - // string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(first)); - EXPECT_THAT(reinterpret_cast<void*>(buf.data() + kPageSize / 2), - EqualsMemory(second)); -} - -TEST_F(MMapFileTest, ReadSharedTruncateDownThenUp) { - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Expand the file to a full page and dirty it. - std::string buf(kPageSize, 'a'); - ASSERT_THAT(Write(buf.c_str(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Map the page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Check that the memory contains the file data. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), buf.c_str(), kPageSize)); - - // Truncate down, then up. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); - - // Check that the memory was zeroed. - std::string zeroed(kPageSize, '\0'); - EXPECT_EQ(0, - memcmp(reinterpret_cast<void*>(addr), zeroed.c_str(), kPageSize)); - - // The file may not actually be updated until msync is called. - ASSERT_THAT(Msync(), SyscallSucceeds()); - - // Prepare to read the entire file contents. - ASSERT_THAT(lseek(fd_.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Expect that the file is fully updated. - std::vector<char> bufFile(kPageSize); - ASSERT_THAT(Read(bufFile.data(), bufFile.size()), - SyscallSucceedsWithValue(bufFile.size())); - EXPECT_EQ(0, memcmp(bufFile.data(), zeroed.c_str(), kPageSize)); -} - -TEST_F(MMapFileTest, WriteSharedTruncateDownThenUp) { - // The file is only half of a page. We map an entire page. Writes to the - // end of the mapping must not be reflected in the file. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - // First half; this will be deleted by truncate(0). - std::string first(kPageSize / 2, 'A'); - memcpy(reinterpret_cast<void*>(addr), first.c_str(), first.size()); - - // Truncate down, then up. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); - - // The whole page is zeroed in memory. - std::string zeroed(kPageSize, '\0'); - EXPECT_EQ(0, - memcmp(reinterpret_cast<void*>(addr), zeroed.c_str(), kPageSize)); - - // The file may not actually be updated until munmap is called. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - // The whole file is also zeroed. - std::vector<char> buf(kPageSize); - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a - // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C - // string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(buf.data()), EqualsMemory(zeroed)); -} - -TEST_F(MMapFileTest, ReadSharedTruncateSIGBUS) { - SetupGvisorDeathTest(); - - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Expand the file to a full page and dirty it. - std::string buf(kPageSize, 'a'); - ASSERT_THAT(Write(buf.c_str(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Map the page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Check that the mapping contains the file data. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), buf.c_str(), kPageSize)); - - // Truncate down. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Accessing the truncated region should cause a SIGBUS. - std::vector<char> in(kPageSize); - EXPECT_EXIT( - std::copy(reinterpret_cast<volatile char*>(addr), - reinterpret_cast<volatile char*>(addr) + kPageSize, in.data()), - ::testing::KilledBySignal(SIGBUS), ""); -} - -TEST_F(MMapFileTest, WriteSharedTruncateSIGBUS) { - SetupGvisorDeathTest(); - - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - // Touch the memory to be sure it really is mapped. - size_t len = strlen(kFileContents); - memcpy(reinterpret_cast<void*>(addr), kFileContents, len); - - // Truncate down. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Accessing the truncated file should cause a SIGBUS. - EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, - reinterpret_cast<volatile char*>(addr)), - ::testing::KilledBySignal(SIGBUS), ""); -} - -TEST_F(MMapFileTest, ReadSharedTruncatePartialPage) { - // Start from scratch. - EXPECT_THAT(ftruncate(fd_.get(), 0), SyscallSucceeds()); - - // Dirty the file. - std::string buf(kPageSize, 'a'); - ASSERT_THAT(Write(buf.c_str(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Map a page. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - // Truncate to half of the page. - EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds()); - - // First half of the page untouched. - EXPECT_EQ(0, - memcmp(reinterpret_cast<void*>(addr), buf.data(), kPageSize / 2)); - - // Second half is zeroed. - std::string zeroed(kPageSize / 2, '\0'); - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize / 2), - zeroed.c_str(), kPageSize / 2)); -} - -// Page can still be accessed and contents are intact after truncating a partial -// page. -TEST_F(MMapFileTest, WriteSharedTruncatePartialPage) { - // Expand the file to a full page. - EXPECT_THAT(ftruncate(fd_.get(), kPageSize), SyscallSucceeds()); - - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - // Fill the entire page. - std::string contents(kPageSize, 'A'); - memcpy(reinterpret_cast<void*>(addr), contents.c_str(), contents.size()); - - // Truncate half of the page. - EXPECT_THAT(ftruncate(fd_.get(), kPageSize / 2), SyscallSucceeds()); - - // First half of the page untouched. - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr), contents.c_str(), - kPageSize / 2)); - - // Second half zeroed. - std::string zeroed(kPageSize / 2, '\0'); - EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(addr + kPageSize / 2), - zeroed.c_str(), kPageSize / 2)); -} - -// MAP_PRIVATE writes are not carried through to the underlying file. -TEST_F(MMapFileTest, WritePrivate) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - fd_.get(), 0), - SyscallSucceeds()); - - size_t len = strlen(kFileContents); - memcpy(reinterpret_cast<void*>(addr), kFileContents, len); - - // The file should not be updated, but if it mistakenly is, it may not be - // until after munmap is called. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - std::vector<char> buf(len); - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - // Cast to void* to avoid EXPECT_THAT assuming buf.data() is a - // NUL-terminated C string. EXPECT_THAT will try to print a char* as a C - // string, possibly overruning the buffer. - EXPECT_THAT(reinterpret_cast<void*>(buf.data()), - EqualsMemory(std::string(len, '\0'))); -} - -// SIGBUS raised when reading or writing past end of a mapped file. -TEST_P(MMapFileParamTest, SigBusDeath) { - SetupGvisorDeathTest(); - - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), - SyscallSucceeds()); - - auto* start = reinterpret_cast<volatile char*>(addr + kPageSize); - - // MMapFileTest makes a file kPageSize/2 long. The entire first page should be - // accessible, but anything beyond it should not. - if (prot() & PROT_WRITE) { - // Write beyond first page. - size_t len = strlen(kFileContents); - EXPECT_EXIT(std::copy(kFileContents, kFileContents + len, start), - ::testing::KilledBySignal(SIGBUS), ""); - } else { - // Read beyond first page. - std::vector<char> in(kPageSize); - EXPECT_EXIT(std::copy(start, start + kPageSize, in.data()), - ::testing::KilledBySignal(SIGBUS), ""); - } -} - -// Tests that SIGBUS is not raised when reading or writing to a file-mapped -// page before EOF, even if part of the mapping extends beyond EOF. -// -// See b/27877699. -TEST_P(MMapFileParamTest, NoSigBusOnPagesBeforeEOF) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), - SyscallSucceeds()); - - // The test passes if this survives. - auto* start = reinterpret_cast<volatile char*>(addr + (kPageSize / 2) + 1); - size_t len = strlen(kFileContents); - if (prot() & PROT_WRITE) { - std::copy(kFileContents, kFileContents + len, start); - } else { - std::vector<char> in(len); - std::copy(start, start + len, in.data()); - } -} - -// Tests that SIGBUS is not raised when reading or writing from a file-mapped -// page containing EOF, *after* the EOF. -TEST_P(MMapFileParamTest, NoSigBusOnPageContainingEOF) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, prot(), flags(), fd_.get(), 0), - SyscallSucceeds()); - - // The test passes if this survives. (Technically addr+kPageSize/2 is already - // beyond EOF, but +1 to check for fencepost errors.) - auto* start = reinterpret_cast<volatile char*>(addr + (kPageSize / 2) + 1); - size_t len = strlen(kFileContents); - if (prot() & PROT_WRITE) { - std::copy(kFileContents, kFileContents + len, start); - } else { - std::vector<char> in(len); - std::copy(start, start + len, in.data()); - } -} - -// Tests that reading from writable shared file-mapped pages succeeds. -// -// On most platforms this is trivial, but when the file is mapped via the sentry -// page cache (which does not yet support writing to shared mappings), a bug -// caused reads to fail unnecessarily on such mappings. See b/28913513. -TEST_F(MMapFileTest, ReadingWritableSharedFilePageSucceeds) { - uintptr_t addr; - size_t len = strlen(kFileContents); - - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - std::vector<char> buf(kPageSize); - // The test passes if this survives. - std::copy(reinterpret_cast<volatile char*>(addr), - reinterpret_cast<volatile char*>(addr) + len, buf.data()); -} - -// Tests that EFAULT is returned when invoking a syscall that requires the OS to -// read past end of file (resulting in a fault in sentry context in the gVisor -// case). See b/28913513. -TEST_F(MMapFileTest, InternalSigBus) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - fd_.get(), 0), - SyscallSucceeds()); - - // This depends on the fact that gVisor implements pipes internally. - int pipefd[2]; - ASSERT_THAT(pipe(pipefd), SyscallSucceeds()); - EXPECT_THAT( - write(pipefd[1], reinterpret_cast<void*>(addr + kPageSize), kPageSize), - SyscallFailsWithErrno(EFAULT)); - - EXPECT_THAT(close(pipefd[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipefd[1]), SyscallSucceeds()); -} - -// Like InternalSigBus, but test the WriteZerosAt path by reading from -// /dev/zero to a shared mapping (so that the SIGBUS isn't caught during -// copy-on-write breaking). -TEST_F(MMapFileTest, InternalSigBusZeroing) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - - const FileDescriptor dev_zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - EXPECT_THAT(read(dev_zero.get(), reinterpret_cast<void*>(addr + kPageSize), - kPageSize), - SyscallFailsWithErrno(EFAULT)); -} - -// Checks that mmaps with a length of uint64_t(-PAGE_SIZE + 1) or greater do not -// induce a sentry panic (due to "rounding up" to 0). -TEST_F(MMapTest, HugeLength) { - EXPECT_THAT(Map(0, static_cast<uint64_t>(-kPageSize + 1), PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallFailsWithErrno(ENOMEM)); -} - -// Tests for a specific gVisor MM caching bug. -TEST_F(MMapTest, AccessCOWInvalidatesCachedSegments) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - auto zero_fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - - // Get a two-page private mapping and fill it with 1s. - uintptr_t addr; - ASSERT_THAT(addr = Map(0, 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), - SyscallSucceeds()); - memset(addr_, 1, 2 * kPageSize); - MaybeSave(); - - // Fork to make the mapping copy-on-write. - pid_t const pid = fork(); - if (pid == 0) { - // The child process waits for the parent to SIGKILL it. - while (true) { - pause(); - } - } - ASSERT_THAT(pid, SyscallSucceeds()); - auto cleanup_child = Cleanup([&] { - EXPECT_THAT(kill(pid, SIGKILL), SyscallSucceeds()); - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - }); - - // Induce a read-only Access of the first page of the mapping, which will not - // cause a copy. The usermem.Segment should be cached. - ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Induce a writable Access of both pages of the mapping. This should - // invalidate the cached Segment. - ASSERT_THAT(PreadFd(zero_fd.get(), addr_, 2 * kPageSize, 0), - SyscallSucceedsWithValue(2 * kPageSize)); - - // Induce a read-only Access of the first page of the mapping again. It should - // read the 0s that were stored in the mapping by the read from /dev/zero. If - // the read failed to invalidate the cached Segment, it will instead read the - // 1s in the stale page. - ASSERT_THAT(PwriteFd(fd.get(), addr_, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - std::vector<char> buf(kPageSize); - ASSERT_THAT(PreadFd(fd.get(), buf.data(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - for (size_t i = 0; i < kPageSize; i++) { - ASSERT_EQ(0, buf[i]) << "at offset " << i; - } -} - -TEST_F(MMapTest, NoReserve) { - const size_t kSize = 10 * 1 << 20; // 10M - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0), - SyscallSucceeds()); - EXPECT_GT(addr, 0); - - // Check that every page can be read/written. Technically, writing to memory - // could SIGSEGV in case there is no more memory available. In gVisor it - // would never happen though because NORESERVE is ignored. In Linux, it's - // possible to fail, but allocation is small enough that it's highly likely - // to succeed. - for (size_t j = 0; j < kSize; j += kPageSize) { - EXPECT_EQ(0, reinterpret_cast<char*>(addr)[j]); - reinterpret_cast<char*>(addr)[j] = j; - } -} - -// Map more than the gVisor page-cache map unit (64k) and ensure that -// it is consistent with reading from the file. -TEST_F(MMapFileTest, Bug38498194) { - // Choose a sufficiently large map unit. - constexpr int kSize = 4 * 1024 * 1024; - EXPECT_THAT(ftruncate(fd_.get(), kSize), SyscallSucceeds()); - - // Map a large enough region so that multiple internal segments - // are created to back the mapping. - uintptr_t addr; - ASSERT_THAT( - addr = Map(0, kSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - - std::vector<char> expect(kSize, 'a'); - std::copy(expect.data(), expect.data() + expect.size(), - reinterpret_cast<volatile char*>(addr)); - - // Trigger writeback for gVisor. In Linux pages stay cached until - // it can't hold onto them anymore. - ASSERT_THAT(Unmap(), SyscallSucceeds()); - - std::vector<char> buf(kSize); - ASSERT_THAT(Read(buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - EXPECT_EQ(buf, expect) << std::string(buf.data(), buf.size()); -} - -// Tests that reading from a file to a memory mapping of the same file does not -// deadlock. See b/34813270. -TEST_F(MMapFileTest, SelfRead) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0), - SyscallSucceeds()); - EXPECT_THAT(Read(reinterpret_cast<char*>(addr), kPageSize / 2), - SyscallSucceedsWithValue(kPageSize / 2)); - // The resulting file contents are poorly-specified and irrelevant. -} - -// Tests that writing to a file from a memory mapping of the same file does not -// deadlock. Regression test for b/34813270. -TEST_F(MMapFileTest, SelfWrite) { - uintptr_t addr; - ASSERT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), 0), - SyscallSucceeds()); - EXPECT_THAT(Write(reinterpret_cast<char*>(addr), kPageSize / 2), - SyscallSucceedsWithValue(kPageSize / 2)); - // The resulting file contents are poorly-specified and irrelevant. -} - -TEST(MMapDeathTest, TruncateAfterCOWBreak) { - SetupGvisorDeathTest(); - - // Create and map a single-page file. - auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDWR)); - ASSERT_THAT(ftruncate(fd.get(), kPageSize), SyscallSucceeds()); - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0)); - - // Write to this mapping, causing the page to be copied for write. - memset(mapping.ptr(), 'a', mapping.len()); - MaybeSave(); // Trigger a co-operative save cycle. - - // Truncate the file and expect it to invalidate the copied page. - ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); - EXPECT_EXIT(*reinterpret_cast<volatile char*>(mapping.ptr()), - ::testing::KilledBySignal(SIGBUS), ""); -} - -// Regression test for #147. -TEST(MMapNoFixtureTest, MapReadOnlyAfterCreateWriteOnly) { - std::string filename = NewTempAbsPath(); - - // We have to create the file O_RDONLY to reproduce the bug because - // fsgofer.localFile.Create() silently upgrades O_WRONLY to O_RDWR, causing - // the cached "write-only" FD to be read/write and therefore usable by mmap(). - auto const ro_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(filename, O_RDONLY | O_CREAT | O_EXCL, 0666)); - - // Get a write-only FD for the same file, which should be ignored by mmap() - // (but isn't in #147). - auto const wo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_WRONLY)); - ASSERT_THAT(ftruncate(wo_fd.get(), kPageSize), SyscallSucceeds()); - - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, ro_fd.get(), 0)); - std::vector<char> buf(kPageSize); - // The test passes if this survives. - std::copy(static_cast<char*>(mapping.ptr()), - static_cast<char*>(mapping.endptr()), buf.data()); -} - -// Conditional on MAP_32BIT. -// This flag is supported only on x86-64, for 64-bit programs. -#ifdef __x86_64__ - -TEST(MMapNoFixtureTest, Map32Bit) { - auto const mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE | MAP_32BIT)); - EXPECT_LT(mapping.addr(), static_cast<uintptr_t>(1) << 32); - EXPECT_LE(mapping.endaddr(), static_cast<uintptr_t>(1) << 32); -} - -#endif // defined(__x86_64__) - -INSTANTIATE_TEST_SUITE_P( - ReadWriteSharedPrivate, MMapFileParamTest, - ::testing::Combine(::testing::ValuesIn({ - PROT_READ, - PROT_WRITE, - PROT_READ | PROT_WRITE, - }), - ::testing::ValuesIn({MAP_SHARED, MAP_PRIVATE}))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc deleted file mode 100644 index 15b645fb7..000000000 --- a/test/syscalls/linux/mount.cc +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <functional> -#include <memory> -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/mount_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(MountTest, MountBadFilesystem) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - // Linux expects a valid target before it checks the file system name. - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(mount("", dir.path().c_str(), "foobar", 0, ""), - SyscallFailsWithErrno(ENODEV)); -} - -TEST(MountTest, MountInvalidTarget) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = NewTempAbsPath(); - EXPECT_THAT(mount("", dir.c_str(), "tmpfs", 0, ""), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(MountTest, MountPermDenied) { - // Clear CAP_SYS_ADMIN. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) { - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false)); - } - - // Linux expects a valid target before checking capability. - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(mount("", dir.path().c_str(), "", 0, ""), - SyscallFailsWithErrno(EPERM)); -} - -TEST(MountTest, UmountPermDenied) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0)); - - // Drop privileges in another thread, so we can still unmount the mounted - // directory. - ScopedThread([&]() { - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false)); - EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EPERM)); - }); -} - -TEST(MountTest, MountOverBusy) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); - - // Should be able to mount over a busy directory. - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0)); -} - -TEST(MountTest, OpenFileBusy) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0)); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); - - // An open file should prevent unmounting. - EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); -} - -TEST(MountTest, UmountDetach) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - // structure: - // - // dir (mount point) - // subdir - // file - // - // We show that we can walk around in the mount after detach-unmount dir. - // - // We show that even though dir is unreachable from outside the mount, we can - // still reach dir's (former) parent! - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - auto mount = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700", - /* umountflags= */ MNT_DETACH)); - const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino) - << "mount point has device number " << before.st_dev - << " and inode number " << before.st_ino << " before and after mount"; - - // Create files in the new mount. - constexpr char kContents[] = "no no no"; - auto const subdir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - auto const file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(dir.path(), kContents, 0777)); - - auto const dir_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(subdir.path(), O_RDONLY | O_DIRECTORY)); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // Unmount the tmpfs. - mount.Release()(); - - // Inode numbers for gofer-accessed files may change across save/restore. - // - // For overlayfs, if xino option is not enabled and if all overlayfs layers do - // not belong to the same filesystem then "the value of st_ino for directory - // objects may not be persistent and could change even while the overlay - // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt - if (!IsRunningWithSaveRestore() && - !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { - const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(before.st_ino, after2.st_ino); - } - - // Can still read file after unmounting. - std::vector<char> buf(sizeof(kContents)); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceeds()); - - // Walk to dir. - auto const mounted_dir = ASSERT_NO_ERRNO_AND_VALUE( - OpenAt(dir_fd.get(), "..", O_DIRECTORY | O_RDONLY)); - // Walk to dir/file. - auto const fd_again = ASSERT_NO_ERRNO_AND_VALUE( - OpenAt(mounted_dir.get(), std::string(Basename(file.path())), O_RDONLY)); - - std::vector<char> buf2(sizeof(kContents)); - EXPECT_THAT(ReadFd(fd_again.get(), buf2.data(), buf2.size()), - SyscallSucceeds()); - EXPECT_EQ(buf, buf2); - - // Walking outside the unmounted realm should still work, too! - auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE( - OpenAt(mounted_dir.get(), "..", O_DIRECTORY | O_RDONLY)); -} - -TEST(MountTest, ActiveSubmountBusy) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0)); - - auto const dir2 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - auto const mount2 = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0)); - - // Since dir now has an active submount, should not be able to unmount. - EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); -} - -TEST(MountTest, MountTmpfs) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // NOTE(b/129868551): Inode IDs are only stable across S/R if we have an open - // FD for that inode. Since we are going to compare inode IDs below, get a - // FileDescriptor for this directory here, which will be closed automatically - // at the end of the test. - auto const fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, O_RDONLY)); - - const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - - { - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0)); - - const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(s.st_mode, S_IFDIR | 0700); - EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino) - << "mount point has device number " << before.st_dev - << " and inode number " << before.st_ino << " before and after mount"; - - EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); - } - - // Now that dir is unmounted again, we should have the old inode back. - // - // Inode numbers for gofer-accessed files may change across save/restore. - // - // For overlayfs, if xino option is not enabled and if all overlayfs layers do - // not belong to the same filesystem then "the value of st_ino for directory - // objects may not be persistent and could change even while the overlay - // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt - if (!IsRunningWithSaveRestore() && - !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) { - const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(before.st_ino, after.st_ino); - } -} - -TEST(MountTest, MountTmpfsMagicValIgnored) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", MS_MGC_VAL, "mode=0700", 0)); -} - -// Passing nullptr to data is equivalent to "". -TEST(MountTest, NullData) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - EXPECT_THAT(mount("", dir.path().c_str(), "tmpfs", 0, nullptr), - SyscallSucceeds()); - EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds()); -} - -TEST(MountTest, MountReadonly) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", MS_RDONLY, "mode=0777", 0)); - - const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path())); - EXPECT_EQ(s.st_mode, S_IFDIR | 0777); - - std::string const filename = JoinPath(dir.path(), "foo"); - EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777), - SyscallFailsWithErrno(EROFS)); -} - -PosixErrorOr<absl::Time> ATime(absl::string_view file) { - struct stat s = {}; - if (stat(std::string(file).c_str(), &s) == -1) { - return PosixError(errno, "stat failed"); - } - return absl::TimeFromTimespec(s.st_atim); -} - -TEST(MountTest, MountNoAtime) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", MS_NOATIME, "mode=0777", 0)); - - std::string const contents = "No no no, don't follow the instructions!"; - auto const file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(dir.path(), contents, 0777)); - - absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path())); - - // Reading from the file should change the atime, but the MS_NOATIME flag - // should prevent that. - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - char buf[100]; - int read_n; - ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds()); - EXPECT_EQ(std::string(buf, read_n), contents); - - absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path())); - - // Expect that atime hasn't changed. - EXPECT_EQ(before, after); -} - -TEST(MountTest, MountNoExec) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const mount = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0777", 0)); - - std::string const contents = "No no no, don't follow the instructions!"; - auto const file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(dir.path(), contents, 0777)); - - int execve_errno; - ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno)); - EXPECT_EQ(execve_errno, EACCES); -} - -TEST(MountTest, RenameRemoveMountPoint) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const dir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path())); - auto const new_dir = NewTempAbsPath(); - - auto const mount = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0)); - - ASSERT_THAT(rename(dir.path().c_str(), new_dir.c_str()), - SyscallFailsWithErrno(EBUSY)); - - ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/mremap.cc b/test/syscalls/linux/mremap.cc deleted file mode 100644 index f0e5f7d82..000000000 --- a/test/syscalls/linux/mremap.cc +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <string.h> -#include <sys/mman.h> - -#include <string> - -#include "gmock/gmock.h" -#include "absl/strings/string_view.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::_; - -namespace gvisor { -namespace testing { - -namespace { - -// Fixture for mremap tests parameterized by mmap flags. -using MremapParamTest = ::testing::TestWithParam<int>; - -TEST_P(MremapParamTest, Noop) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize, 0, nullptr), - IsPosixErrorOkAndHolds(m.ptr())); - EXPECT_TRUE(IsMapped(m.addr())); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingWholeVMA) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // N.B. we must be in a single-threaded subprocess to ensure a - // background thread doesn't concurrently map the second page. - void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingPartialVMA) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - void* addr = mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ShrinkingAcrossVMAs) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam())); - // Changing permissions on the first page forces it to become a separate vma. - ASSERT_THAT(mprotect(m.ptr(), kPageSize, PROT_NONE), SyscallSucceeds()); - - const auto rest = [&] { - // Both old_size and new_size now span two vmas; mremap - // shouldn't care. - void* addr = mremap(m.ptr(), 3 * kPageSize, 2 * kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(m.addr() + kPageSize)); - TEST_CHECK(!IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ExpansionSuccess) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page so that the first can be expanded back into it. - // - // N.B. we must be in a single-threaded subprocess to ensure a - // background thread doesn't concurrently map this page. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(m.ptr(), kPageSize, 2 * kPageSize, 0, nullptr); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == m.ptr()); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(m.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, InPlace_ExpansionFailure) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page, leaving a one-page hole. Trying to expand the - // first page to three pages should fail since the original third page - // is still mapped. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(m.ptr(), kPageSize, 3 * kPageSize, 0, nullptr); - TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded"); - TEST_PCHECK_MSG(errno == ENOMEM, "mremap failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, MayMove_Expansion) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap the second page, leaving a one-page hole. Trying to expand the - // first page to three pages with MREMAP_MAYMOVE should force the - // mapping to be relocated since the original third page is still - // mapped. - TEST_PCHECK( - munmap(reinterpret_cast<void*>(m.addr() + kPageSize), kPageSize) == 0); - MaybeSave(); - - void* addr2 = - mremap(m.ptr(), kPageSize, 3 * kPageSize, MREMAP_MAYMOVE, nullptr); - TEST_PCHECK_MSG(addr2 != MAP_FAILED, "mremap failed"); - MaybeSave(); - - const Mapping m2 = Mapping(addr2, 3 * kPageSize); - TEST_CHECK(m.addr() != m2.addr()); - - TEST_CHECK(!IsMapped(m.addr())); - TEST_CHECK(!IsMapped(m.addr() + kPageSize)); - TEST_CHECK(IsMapped(m.addr() + 2 * kPageSize)); - TEST_CHECK(IsMapped(m2.addr())); - TEST_CHECK(IsMapped(m2.addr() + kPageSize)); - TEST_CHECK(IsMapped(m2.addr() + 2 * kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_SourceAndDestinationCannotOverlap) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - ASSERT_THAT(Mremap(m.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, m.ptr()), - PosixErrorIs(EINVAL, _)); - EXPECT_TRUE(IsMapped(m.addr())); -} - -TEST_P(MremapParamTest, Fixed_SameSize) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst to create a hole. - TEST_PCHECK(munmap(dst.ptr(), kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_SameSize_Unmapping) { - // Like the Fixed_SameSize case, but expect mremap to unmap the destination - // automatically. - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - void* addr = mremap(src.ptr(), kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingWholeVMA) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap does not keep the - // second page. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(!IsMapped(src.addr() + kPageSize)); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingPartialVMA) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap does not keep the - // second page. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), 2 * kPageSize, kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(!IsMapped(src.addr() + kPageSize)); - TEST_CHECK(IsMapped(src.addr() + 2 * kPageSize)); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_ShrinkingAcrossVMAs) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(3 * kPageSize, PROT_READ, GetParam())); - // Changing permissions on the first page forces it to become a separate vma. - ASSERT_THAT(mprotect(src.ptr(), kPageSize, PROT_NONE), SyscallSucceeds()); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unlike flags=0, MREMAP_FIXED requires that [old_address, - // old_address+new_size) only spans a single vma. - void* addr = mremap(src.ptr(), 3 * kPageSize, 2 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_CHECK_MSG(addr == MAP_FAILED, "mremap unexpectedly succeeded"); - TEST_PCHECK_MSG(errno == EFAULT, "mremap failed with wrong errno"); - MaybeSave(); - - TEST_CHECK(IsMapped(src.addr())); - TEST_CHECK(IsMapped(src.addr() + kPageSize)); - // Despite failing, mremap should have unmapped [old_address+new_size, - // old_address+old_size) (i.e. the third page). - TEST_CHECK(!IsMapped(src.addr() + 2 * kPageSize)); - // Despite failing, mremap should have unmapped the destination pages. - TEST_CHECK(!IsMapped(dst.addr())); - TEST_CHECK(!IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST_P(MremapParamTest, Fixed_Expansion) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, GetParam())); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(2 * kPageSize, PROT_NONE, GetParam())); - - const auto rest = [&] { - // Unmap dst so we can check that mremap actually maps all pages - // at the destination. - TEST_PCHECK(munmap(dst.ptr(), 2 * kPageSize) == 0); - MaybeSave(); - - void* addr = mremap(src.ptr(), kPageSize, 2 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()); - TEST_PCHECK_MSG(addr != MAP_FAILED, "mremap failed"); - TEST_CHECK(addr == dst.ptr()); - MaybeSave(); - - TEST_CHECK(!IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - TEST_CHECK(IsMapped(dst.addr() + kPageSize)); - }; - - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -INSTANTIATE_TEST_SUITE_P(PrivateShared, MremapParamTest, - ::testing::Values(MAP_PRIVATE, MAP_SHARED)); - -// mremap with old_size == 0 only works with MAP_SHARED after Linux 4.14 -// (dba58d3b8c50 "mm/mremap: fail map duplication attempts for private -// mappings"). - -TEST(MremapTest, InPlace_Copy) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - EXPECT_THAT(Mremap(m.ptr(), 0, kPageSize, 0, nullptr), - PosixErrorIs(ENOMEM, _)); -} - -TEST(MremapTest, MayMove_Copy) { - Mapping const m = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - - // Remainder of this test executes in a subprocess to ensure that if mremap - // incorrectly removes m, it is not remapped by another thread. - const auto rest = [&] { - void* ptr = mremap(m.ptr(), 0, kPageSize, MREMAP_MAYMOVE, nullptr); - MaybeSave(); - TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed"); - TEST_CHECK(ptr != m.ptr()); - TEST_CHECK(IsMapped(m.addr())); - TEST_CHECK(IsMapped(reinterpret_cast<uintptr_t>(ptr))); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -TEST(MremapTest, MustMove_Copy) { - Mapping const src = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_SHARED)); - Mapping const dst = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Remainder of this test executes in a subprocess to ensure that if mremap - // incorrectly removes src, it is not remapped by another thread. - const auto rest = [&] { - void* ptr = mremap(src.ptr(), 0, kPageSize, MREMAP_MAYMOVE | MREMAP_FIXED, - dst.ptr()); - MaybeSave(); - TEST_PCHECK_MSG(ptr != MAP_FAILED, "mremap failed"); - TEST_CHECK(ptr == dst.ptr()); - TEST_CHECK(IsMapped(src.addr())); - TEST_CHECK(IsMapped(dst.addr())); - }; - EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); -} - -void ExpectAllBytesAre(absl::string_view v, char c) { - for (size_t i = 0; i < v.size(); i++) { - ASSERT_EQ(v[i], c) << "at offset " << i; - } -} - -TEST(MremapTest, ExpansionPreservesCOWPagesAndExposesNewFilePages) { - // Create a file with 3 pages. The first is filled with 'a', the second is - // filled with 'b', and the third is filled with 'c'. - TempPath const file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'a').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'b').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(WriteFd(fd.get(), std::string(kPageSize, 'c').c_str(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - - // Create a private mapping of the first 2 pages, and fill the second page - // with 'd'. - Mapping const src = ASSERT_NO_ERRNO_AND_VALUE(Mmap(nullptr, 2 * kPageSize, - PROT_READ | PROT_WRITE, - MAP_PRIVATE, fd.get(), 0)); - memset(reinterpret_cast<void*>(src.addr() + kPageSize), 'd', kPageSize); - MaybeSave(); - - // Move the mapping while expanding it to 3 pages. The resulting mapping - // should contain the original first page of the file (filled with 'a'), - // followed by the private copy of the second page (filled with 'd'), followed - // by the newly-mapped third page of the file (filled with 'c'). - Mapping const dst = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(3 * kPageSize, PROT_NONE, MAP_PRIVATE)); - ASSERT_THAT(Mremap(src.ptr(), 2 * kPageSize, 3 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, dst.ptr()), - IsPosixErrorOkAndHolds(dst.ptr())); - auto const v = dst.view(); - ExpectAllBytesAre(v.substr(0, kPageSize), 'a'); - ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'd'); - ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), 'c'); -} - -TEST(MremapDeathTest, SharedAnon) { - SetupGvisorDeathTest(); - - // Reserve 4 pages of address space. - Mapping const reserved = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(4 * kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Create a 2-page shared anonymous mapping at the beginning of the - // reservation. Fill the first page with 'a' and the second with 'b'. - Mapping const m = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(reserved.ptr(), 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0)); - memset(m.ptr(), 'a', kPageSize); - memset(reinterpret_cast<void*>(m.addr() + kPageSize), 'b', kPageSize); - MaybeSave(); - - // Shrink the mapping to 1 page in-place. - ASSERT_THAT(Mremap(m.ptr(), 2 * kPageSize, kPageSize, 0, m.ptr()), - IsPosixErrorOkAndHolds(m.ptr())); - - // Expand the mapping to 3 pages, moving it forward by 1 page in the process - // since the old and new mappings can't overlap. - void* const new_m = reinterpret_cast<void*>(m.addr() + kPageSize); - ASSERT_THAT(Mremap(m.ptr(), kPageSize, 3 * kPageSize, - MREMAP_MAYMOVE | MREMAP_FIXED, new_m), - IsPosixErrorOkAndHolds(new_m)); - - // The first 2 pages of the mapping should still contain the data we wrote - // (i.e. shrinking should not have discarded the second page's data), while - // touching the third page should raise SIGBUS. - auto const v = - absl::string_view(static_cast<char const*>(new_m), 3 * kPageSize); - ExpectAllBytesAre(v.substr(0, kPageSize), 'a'); - ExpectAllBytesAre(v.substr(kPageSize, kPageSize), 'b'); - EXPECT_EXIT(ExpectAllBytesAre(v.substr(2 * kPageSize, kPageSize), '\0'), - ::testing::KilledBySignal(SIGBUS), ""); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/msync.cc b/test/syscalls/linux/msync.cc deleted file mode 100644 index 2b2b6aef9..000000000 --- a/test/syscalls/linux/msync.cc +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/mman.h> -#include <unistd.h> - -#include <functional> -#include <string> -#include <utility> -#include <vector> - -#include "test/util/file_descriptor.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Parameters for msync tests. Use a std::tuple so we can use -// ::testing::Combine. -using MsyncTestParam = - std::tuple<int, // msync flags - std::function<PosixErrorOr<Mapping>()> // returns mapping to - // msync - >; - -class MsyncParameterizedTest : public ::testing::TestWithParam<MsyncTestParam> { - protected: - int msync_flags() const { return std::get<0>(GetParam()); } - - PosixErrorOr<Mapping> GetMapping() const { return std::get<1>(GetParam())(); } -}; - -// All valid msync(2) flag combinations, not including MS_INVALIDATE. ("Linux -// permits a call to msync() that specifies neither [MS_SYNC or MS_ASYNC], with -// semantics that are (currently) equivalent to specifying MS_ASYNC." - -// msync(2)) -constexpr std::initializer_list<int> kMsyncFlags = {MS_SYNC, MS_ASYNC, 0}; - -// Returns functions that return mappings that should be successfully -// msync()able. -std::vector<std::function<PosixErrorOr<Mapping>()>> SyncableMappings() { - std::vector<std::function<PosixErrorOr<Mapping>()>> funcs; - for (bool const writable : {false, true}) { - for (int const mflags : {MAP_PRIVATE, MAP_SHARED}) { - int const prot = PROT_READ | (writable ? PROT_WRITE : 0); - int const oflags = O_CREAT | (writable ? O_RDWR : O_RDONLY); - funcs.push_back([=] { return MmapAnon(kPageSize, prot, mflags); }); - funcs.push_back([=]() -> PosixErrorOr<Mapping> { - std::string const path = NewTempAbsPath(); - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, oflags, 0644)); - // Don't unlink the file since that breaks save/restore. Just let the - // test infrastructure clean up all of our temporary files when we're - // done. - return Mmap(nullptr, kPageSize, prot, mflags, fd.get(), 0); - }); - } - } - return funcs; -} - -PosixErrorOr<Mapping> NoMappings() { - return PosixError(EINVAL, "unexpected attempt to create a mapping"); -} - -// "Fixture" for msync tests that hold for all valid flags, but do not create -// mappings. -using MsyncNoMappingTest = MsyncParameterizedTest; - -TEST_P(MsyncNoMappingTest, UnmappedAddressWithZeroLengthSucceeds) { - EXPECT_THAT(msync(nullptr, 0, msync_flags()), SyscallSucceeds()); -} - -TEST_P(MsyncNoMappingTest, UnmappedAddressWithNonzeroLengthFails) { - EXPECT_THAT(msync(nullptr, kPageSize, msync_flags()), - SyscallFailsWithErrno(ENOMEM)); -} - -INSTANTIATE_TEST_SUITE_P(All, MsyncNoMappingTest, - ::testing::Combine(::testing::ValuesIn(kMsyncFlags), - ::testing::Values(NoMappings))); - -// "Fixture" for msync tests that are not parameterized by msync flags, but do -// create mappings. -using MsyncNoFlagsTest = MsyncParameterizedTest; - -TEST_P(MsyncNoFlagsTest, BothSyncAndAsyncFails) { - auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping()); - EXPECT_THAT(msync(m.ptr(), m.len(), MS_SYNC | MS_ASYNC), - SyscallFailsWithErrno(EINVAL)); -} - -INSTANTIATE_TEST_SUITE_P( - All, MsyncNoFlagsTest, - ::testing::Combine(::testing::Values(0), // ignored - ::testing::ValuesIn(SyncableMappings()))); - -// "Fixture" for msync tests parameterized by both msync flags and sources of -// mappings. -using MsyncFullParamTest = MsyncParameterizedTest; - -TEST_P(MsyncFullParamTest, NormallySucceeds) { - auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping()); - EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags()), SyscallSucceeds()); -} - -TEST_P(MsyncFullParamTest, UnalignedLengthSucceeds) { - auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping()); - EXPECT_THAT(msync(m.ptr(), m.len() - 1, msync_flags()), SyscallSucceeds()); -} - -TEST_P(MsyncFullParamTest, UnalignedAddressFails) { - auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping()); - EXPECT_THAT( - msync(reinterpret_cast<void*>(m.addr() + 1), m.len() - 1, msync_flags()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(MsyncFullParamTest, InvalidateUnlockedSucceeds) { - auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping()); - EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags() | MS_INVALIDATE), - SyscallSucceeds()); -} - -// The test for MS_INVALIDATE on mlocked pages is in mlock.cc since it requires -// probing for mlock support. - -INSTANTIATE_TEST_SUITE_P( - All, MsyncFullParamTest, - ::testing::Combine(::testing::ValuesIn(kMsyncFlags), - ::testing::ValuesIn(SyncableMappings()))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/munmap.cc b/test/syscalls/linux/munmap.cc deleted file mode 100644 index 067241f4d..000000000 --- a/test/syscalls/linux/munmap.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/mman.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class MunmapTest : public ::testing::Test { - protected: - void SetUp() override { - m_ = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - ASSERT_NE(MAP_FAILED, m_); - } - - void* m_ = nullptr; -}; - -TEST_F(MunmapTest, HappyCase) { - EXPECT_THAT(munmap(m_, kPageSize), SyscallSucceeds()); -} - -TEST_F(MunmapTest, ZeroLength) { - EXPECT_THAT(munmap(m_, 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(MunmapTest, LastPageRoundUp) { - // Attempt to unmap up to and including the last page. - EXPECT_THAT(munmap(m_, static_cast<size_t>(-kPageSize + 1)), - SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/network_namespace.cc b/test/syscalls/linux/network_namespace.cc deleted file mode 100644 index 133fdecf0..000000000 --- a/test/syscalls/linux/network_namespace.cc +++ /dev/null @@ -1,52 +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. - -#include <net/if.h> -#include <sched.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -TEST(NetworkNamespaceTest, LoopbackExists) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - ScopedThread t([&] { - ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0)); - - // TODO(gvisor.dev/issue/1833): Update this to test that only "lo" exists. - // Check loopback device exists. - int sock = socket(AF_INET, SOCK_DGRAM, 0); - ASSERT_THAT(sock, SyscallSucceeds()); - struct ifreq ifr; - strncpy(ifr.ifr_name, "lo", IFNAMSIZ); - EXPECT_THAT(ioctl(sock, SIOCGIFINDEX, &ifr), SyscallSucceeds()) - << "lo cannot be found"; - }); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc deleted file mode 100644 index e65ffee8f..000000000 --- a/test/syscalls/linux/open.cc +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/capability.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// This test is currently very rudimentary. -// -// There are plenty of extra cases to cover once the sentry supports them. -// -// Different types of opens: -// * O_CREAT -// * O_DIRECTORY -// * O_NOFOLLOW -// * O_PATH -// -// Special operations on open: -// * O_EXCL -// -// Special files: -// * Blocking behavior for a named pipe. -// -// Different errors: -// * EACCES -// * EEXIST -// * ENAMETOOLONG -// * ELOOP -// * ENOTDIR -// * EPERM -class OpenTest : public FileTest { - void SetUp() override { - FileTest::SetUp(); - - ASSERT_THAT( - write(test_file_fd_.get(), test_data_.c_str(), test_data_.length()), - SyscallSucceedsWithValue(test_data_.length())); - EXPECT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), SyscallSucceeds()); - } - - public: - const std::string test_data_ = "hello world\n"; -}; - -TEST_F(OpenTest, OTrunc) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(open(dir.path().c_str(), O_TRUNC, 0666), - SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(OpenTest, OTruncAndReadOnlyDir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(open(dir.path().c_str(), O_TRUNC | O_RDONLY, 0666), - SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(OpenTest, OTruncAndReadOnlyFile) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto path = JoinPath(dir.path(), "foo"); - EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); - EXPECT_NO_ERRNO(Open(path, O_TRUNC | O_RDONLY, 0666)); -} - -TEST_F(OpenTest, OCreateDirectory) { - SKIP_IF(IsRunningWithVFS1()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Normal case: existing directory. - ASSERT_THAT(open(dir.path().c_str(), O_RDWR | O_CREAT, 0666), - SyscallFailsWithErrno(EISDIR)); - // Trailing separator on existing directory. - ASSERT_THAT(open(dir.path().append("/").c_str(), O_RDWR | O_CREAT, 0666), - SyscallFailsWithErrno(EISDIR)); - // Trailing separator on non-existing directory. - ASSERT_THAT(open(JoinPath(dir.path(), "non-existent").append("/").c_str(), - O_RDWR | O_CREAT, 0666), - SyscallFailsWithErrno(EISDIR)); - // "." special case. - ASSERT_THAT(open(JoinPath(dir.path(), ".").c_str(), O_RDWR | O_CREAT, 0666), - SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(OpenTest, MustCreateExisting) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Existing directory. - ASSERT_THAT(open(dir.path().c_str(), O_RDWR | O_CREAT | O_EXCL, 0666), - SyscallFailsWithErrno(EEXIST)); - - // Existing file. - auto newFile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - ASSERT_THAT(open(newFile.path().c_str(), O_RDWR | O_CREAT | O_EXCL, 0666), - SyscallFailsWithErrno(EEXIST)); -} - -TEST_F(OpenTest, ReadOnly) { - char buf; - const FileDescriptor ro_file = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - - EXPECT_THAT(read(ro_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_THAT(lseek(ro_file.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(write(ro_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(OpenTest, WriteOnly) { - char buf; - const FileDescriptor wo_file = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY)); - - EXPECT_THAT(read(wo_file.get(), &buf, 1), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(lseek(wo_file.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(write(wo_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); -} - -TEST_F(OpenTest, CreateWithAppend) { - std::string data = "text"; - std::string new_file = NewTempAbsPath(); - const FileDescriptor file = ASSERT_NO_ERRNO_AND_VALUE( - Open(new_file, O_WRONLY | O_APPEND | O_CREAT, 0666)); - EXPECT_THAT(write(file.get(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - EXPECT_THAT(lseek(file.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(write(file.get(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - - // Check that the size of the file is correct and that the offset has been - // incremented to that size. - struct stat s0; - EXPECT_THAT(fstat(file.get(), &s0), SyscallSucceeds()); - EXPECT_EQ(s0.st_size, 2 * data.size()); - EXPECT_THAT(lseek(file.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(2 * data.size())); -} - -TEST_F(OpenTest, ReadWrite) { - char buf; - const FileDescriptor rw_file = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - EXPECT_THAT(read(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); - EXPECT_THAT(lseek(rw_file.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(write(rw_file.get(), &buf, 1), SyscallSucceedsWithValue(1)); -} - -TEST_F(OpenTest, RelPath) { - auto name = std::string(Basename(test_file_name_)); - - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name, O_RDONLY)); -} - -TEST_F(OpenTest, AbsPath) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); -} - -TEST_F(OpenTest, AtRelPath) { - auto name = std::string(Basename(test_file_name_)); - const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( - Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), name, O_RDONLY)); -} - -TEST_F(OpenTest, AtAbsPath) { - const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( - Open(GetAbsoluteTestTmpdir(), O_RDONLY | O_DIRECTORY)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(dirfd.get(), test_file_name_, O_RDONLY)); -} - -TEST_F(OpenTest, OpenNoFollowSymlink) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string link_path = JoinPath(dir.path().c_str(), "link"); - ASSERT_THAT(symlink(test_file_name_.c_str(), link_path.c_str()), - SyscallSucceeds()); - auto cleanup = Cleanup([link_path]() { - EXPECT_THAT(unlink(link_path.c_str()), SyscallSucceeds()); - }); - - // Open will succeed without O_NOFOLLOW and fails with O_NOFOLLOW. - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open(link_path, O_RDONLY)); - ASSERT_THAT(open(link_path.c_str(), O_RDONLY | O_NOFOLLOW), - SyscallFailsWithErrno(ELOOP)); -} - -TEST_F(OpenTest, OpenNoFollowStillFollowsLinksInPath) { - // We will create the following structure: - // tmp_folder/real_folder/file - // tmp_folder/sym_folder -> tmp_folder/real_folder - // - // We will then open tmp_folder/sym_folder/file with O_NOFOLLOW and it - // should succeed as O_NOFOLLOW only applies to the final path component. - auto tmp_path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto sym_path = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), tmp_path.path())); - auto file_path = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_path.path())); - - auto path_via_symlink = JoinPath(sym_path.path(), Basename(file_path.path())); - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open(path_via_symlink, O_RDONLY | O_NOFOLLOW)); -} - -// Test that open(2) can follow symlinks that point back to the same tree. -// Test sets up files as follows: -// root/child/symlink => redirects to ../.. -// root/child/target => regular file -// -// open("root/child/symlink/root/child/file") -TEST_F(OpenTest, SymlinkRecurse) { - auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); - auto symlink = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(child.path(), "../..")); - auto target = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(child.path(), "abc", 0644)); - auto path_via_symlink = - JoinPath(symlink.path(), Basename(root.path()), Basename(child.path()), - Basename(target.path())); - const auto contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContents(path_via_symlink)); - ASSERT_EQ(contents, "abc"); -} - -TEST_F(OpenTest, Fault) { - char* totally_not_null = nullptr; - ASSERT_THAT(open(totally_not_null, O_RDONLY), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(OpenTest, AppendOnly) { - // First write some data to the fresh file. - const int64_t kBufSize = 1024; - std::vector<char> buf(kBufSize, 'a'); - - FileDescriptor fd0 = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - - std::fill(buf.begin(), buf.end(), 'a'); - EXPECT_THAT(WriteFd(fd0.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - fd0.reset(); // Close the file early. - - // Next get two handles to the same file. We open two files because we want - // to make sure that appending is respected between them. - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND)); - EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND)); - EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - // Then try to write to the first fd and make sure the bytes are appended. - EXPECT_THAT(WriteFd(fd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Check that the size of the file is correct and that the offset has been - // incremented to that size. - struct stat s0; - EXPECT_THAT(fstat(fd1.get(), &s0), SyscallSucceeds()); - EXPECT_EQ(s0.st_size, kBufSize * 2); - EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(kBufSize * 2)); - - // Then try to write to the second fd and make sure the bytes are appended. - EXPECT_THAT(WriteFd(fd2.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Check that the size of the file is correct and that the offset has been - // incremented to that size. - struct stat s1; - EXPECT_THAT(fstat(fd2.get(), &s1), SyscallSucceeds()); - EXPECT_EQ(s1.st_size, kBufSize * 3); - EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(kBufSize * 3)); -} - -TEST_F(OpenTest, AppendConcurrentWrite) { - constexpr int kThreadCount = 5; - constexpr int kBytesPerThread = 10000; - std::unique_ptr<ScopedThread> threads[kThreadCount]; - - // In case of the uncached policy, we expect that a file system can be changed - // externally, so we create a new inode each time when we open a file and we - // can't guarantee that writes to files with O_APPEND will work correctly. - SKIP_IF(getenv("GVISOR_GOFER_UNCACHED")); - - EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds()); - - std::string filename = test_file_name_; - DisableSave ds; // Too many syscalls. - // Start kThreadCount threads which will write concurrently into the same - // file. - for (int i = 0; i < kThreadCount; i++) { - threads[i] = absl::make_unique<ScopedThread>([filename]() { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR | O_APPEND)); - - for (int j = 0; j < kBytesPerThread; j++) { - EXPECT_THAT(WriteFd(fd.get(), &j, 1), SyscallSucceedsWithValue(1)); - } - }); - } - for (int i = 0; i < kThreadCount; i++) { - threads[i]->Join(); - } - - // Check that the size of the file is correct. - struct stat st; - EXPECT_THAT(stat(test_file_name_.c_str(), &st), SyscallSucceeds()); - EXPECT_EQ(st.st_size, kThreadCount * kBytesPerThread); -} - -TEST_F(OpenTest, Truncate) { - { - // First write some data to the new file and close it. - FileDescriptor fd0 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY)); - std::vector<char> orig(10, 'a'); - EXPECT_THAT(WriteFd(fd0.get(), orig.data(), orig.size()), - SyscallSucceedsWithValue(orig.size())); - } - - // Then open with truncate and verify that offset is set to 0. - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_TRUNC)); - EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - // Then write less data to the file and ensure the old content is gone. - std::vector<char> want(5, 'b'); - EXPECT_THAT(WriteFd(fd1.get(), want.data(), want.size()), - SyscallSucceedsWithValue(want.size())); - - struct stat stat; - EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); - EXPECT_EQ(stat.st_size, want.size()); - EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(want.size())); - - // Read the data and ensure only the latest write is in the file. - std::vector<char> got(want.size() + 1, 'c'); - ASSERT_THAT(pread(fd1.get(), got.data(), got.size(), 0), - SyscallSucceedsWithValue(want.size())); - EXPECT_EQ(memcmp(want.data(), got.data(), want.size()), 0) - << "rbuf=" << got.data(); - EXPECT_EQ(got.back(), 'c'); // Last byte should not have been modified. -} - -TEST_F(OpenTest, NameTooLong) { - char buf[4097] = {}; - memset(buf, 'a', 4097); - EXPECT_THAT(open(buf, O_RDONLY), SyscallFailsWithErrno(ENAMETOOLONG)); -} - -TEST_F(OpenTest, DotsFromRoot) { - const FileDescriptor rootfd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/", O_RDONLY | O_DIRECTORY)); - const FileDescriptor other_rootfd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(rootfd.get(), "..", O_RDONLY)); -} - -TEST_F(OpenTest, DirectoryWritableFails) { - ASSERT_THAT(open(GetAbsoluteTestTmpdir().c_str(), O_RDWR), - SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(OpenTest, FileNotDirectory) { - // Create a file and try to open it with O_DIRECTORY. - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_THAT(open(file.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallFailsWithErrno(ENOTDIR)); -} - -TEST_F(OpenTest, SymlinkDirectory) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string link = NewTempAbsPath(); - ASSERT_THAT(symlink(dir.path().c_str(), link.c_str()), SyscallSucceeds()); - ASSERT_NO_ERRNO(Open(link, O_RDONLY | O_DIRECTORY)); -} - -TEST_F(OpenTest, Null) { - char c = '\0'; - ASSERT_THAT(open(&c, O_RDONLY), SyscallFailsWithErrno(ENOENT)); -} - -// NOTE(b/119785738): While the man pages specify that this behavior should be -// undefined, Linux truncates the file on opening read only if we have write -// permission, so we will too. -TEST_F(OpenTest, CanTruncateReadOnly) { - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY | O_TRUNC)); - - struct stat stat; - EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); - EXPECT_EQ(stat.st_size, 0); -} - -// If we don't have read permission on the file, opening with -// O_TRUNC should fail. -TEST_F(OpenTest, CanTruncateReadOnlyNoWritePermission_NoRandomSave) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - const DisableSave ds; // Permissions are dropped. - ASSERT_THAT(chmod(test_file_name_.c_str(), S_IRUSR | S_IRGRP), - SyscallSucceeds()); - - ASSERT_THAT(open(test_file_name_.c_str(), O_RDONLY | O_TRUNC), - SyscallFailsWithErrno(EACCES)); - - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - - struct stat stat; - EXPECT_THAT(fstat(fd1.get(), &stat), SyscallSucceeds()); - EXPECT_EQ(stat.st_size, test_data_.size()); -} - -// If we don't have read permission but have write permission, opening O_WRONLY -// and O_TRUNC should succeed. -TEST_F(OpenTest, CanTruncateWriteOnlyNoReadPermission_NoRandomSave) { - const DisableSave ds; // Permissions are dropped. - - EXPECT_THAT(fchmod(test_file_fd_.get(), S_IWUSR | S_IWGRP), - SyscallSucceeds()); - - const FileDescriptor fd1 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_WRONLY | O_TRUNC)); - - EXPECT_THAT(fchmod(test_file_fd_.get(), S_IRUSR | S_IRGRP), - SyscallSucceeds()); - - const FileDescriptor fd2 = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - - struct stat stat; - EXPECT_THAT(fstat(fd2.get(), &stat), SyscallSucceeds()); - EXPECT_EQ(stat.st_size, 0); -} - -TEST_F(OpenTest, CanTruncateWithStrangePermissions) { - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - const DisableSave ds; // Permissions are dropped. - std::string path = NewTempAbsPath(); - // Create a file without user permissions. - EXPECT_NO_ERRNO(Open(path, O_CREAT | O_TRUNC | O_WRONLY, 055)); - - // Cannot open file because we are owner and have no permissions set. - EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); - - // We *can* chmod the file, because we are the owner. - EXPECT_THAT(chmod(path.c_str(), 0755), SyscallSucceeds()); - - // Now we can open the file again. - EXPECT_NO_ERRNO(Open(path, O_RDWR)); -} - -TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string bad_path = file.path() + "/"; - EXPECT_THAT(open(bad_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ENOTDIR)); -} - -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)); -} - -TEST_F(OpenTest, OpenWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - const DisableSave ds; // Permissions are dropped. - std::string path = NewTempAbsPath(); - - // Create a file without user permissions. - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055)); - - // Cannot open file as read only because we are owner and have no permissions - // set. - EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); - - // Can open file with O_PATH because don't need permissions on the object when - // opening with O_PATH. - ASSERT_NO_ERRNO(Open(path, O_PATH)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc deleted file mode 100644 index 46f41de50..000000000 --- a/test/syscalls/linux/open_create.cc +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { -TEST(CreateTest, TmpFile) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "a"), O_RDWR | O_CREAT, 0666)); -} - -TEST(CreateTest, ExistingFile) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto path = JoinPath(dir.path(), "ExistingFile"); - EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); - EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); -} - -TEST(CreateTest, CreateAtFile) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dirfd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, 0666)); - int fd; - EXPECT_THAT(fd = openat(dirfd.get(), "CreateAtFile", O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(CreateTest, HonorsUmask_NoRandomSave) { - const DisableSave ds; // file cannot be re-opened as writable. - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempUmask mask(0222); - auto fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(dir.path(), "UmaskedFile"), O_RDWR | O_CREAT, 0666)); - struct stat statbuf; - ASSERT_THAT(fstat(fd.get(), &statbuf), SyscallSucceeds()); - EXPECT_EQ(0444, statbuf.st_mode & 0777); -} - -TEST(CreateTest, CreateExclusively) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto path = JoinPath(dir.path(), "foo"); - EXPECT_NO_ERRNO(Open(path, O_CREAT | O_RDWR, 0644)); - EXPECT_THAT(open(path.c_str(), O_CREAT | O_EXCL | O_RDWR, 0644), - SyscallFailsWithErrno(EEXIST)); -} - -TEST(CreateTest, CreatWithOTrunc) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(open(dir.path().c_str(), O_CREAT | O_TRUNC, 0666), - SyscallFailsWithErrno(EISDIR)); -} - -TEST(CreateTest, CreatDirWithOTruncAndReadOnly) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(open(dir.path().c_str(), O_CREAT | O_TRUNC | O_RDONLY, 0666), - SyscallFailsWithErrno(EISDIR)); -} - -TEST(CreateTest, CreatFileWithOTruncAndReadOnly) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto path = JoinPath(dir.path(), "foo"); - ASSERT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); - ASSERT_NO_ERRNO(Open(path, O_CREAT | O_TRUNC | O_RDONLY, 0666)); -} - -TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // always override directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - auto parent = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555)); - auto file = JoinPath(parent.path(), "foo"); - ASSERT_THAT(open(file.c_str(), O_CREAT | O_RDWR, 0644), - SyscallFailsWithErrno(EACCES)); -} - -// A file originally created RW, but opened RO can later be opened RW. -// Regression test for b/65385065. -TEST(CreateTest, OpenCreateROThenRW) { - TempPath file(NewTempAbsPath()); - - // Create a RW file, but only open it RO. - FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE( - Open(file.path(), O_CREAT | O_EXCL | O_RDONLY, 0644)); - - // Now get a RW FD. - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - // fd1 is not writable, but fd2 is. - char c = 'a'; - EXPECT_THAT(WriteFd(fd1.get(), &c, 1), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(WriteFd(fd2.get(), &c, 1), SyscallSucceedsWithValue(1)); -} - -TEST(CreateTest, ChmodReadToWriteBetweenOpens_NoRandomSave) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // override file read/write permissions. CAP_DAC_READ_SEARCH needs to be - // cleared for the same reason. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400)); - - const FileDescriptor rfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // Cannot restore after making permissions more restrictive. - const DisableSave ds; - ASSERT_THAT(fchmod(rfd.get(), 0200), SyscallSucceeds()); - - EXPECT_THAT(open(file.path().c_str(), O_RDONLY), - SyscallFailsWithErrno(EACCES)); - - const FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - - char c = 'x'; - EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - c = 0; - EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(c, 'x'); -} - -TEST(CreateTest, ChmodWriteToReadBetweenOpens_NoRandomSave) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // override file read/write permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0200)); - - const FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - - // Cannot restore after making permissions more restrictive. - const DisableSave ds; - ASSERT_THAT(fchmod(wfd.get(), 0400), SyscallSucceeds()); - - EXPECT_THAT(open(file.path().c_str(), O_WRONLY), - SyscallFailsWithErrno(EACCES)); - - const FileDescriptor rfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - char c = 'x'; - EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - c = 0; - EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(c, 'x'); -} - -TEST(CreateTest, CreateWithReadFlagNotAllowedByMode_NoRandomSave) { - // The only time we can open a file with flags forbidden by its permissions - // is when we are creating the file. We cannot re-open with the same flags, - // so we cannot restore an fd obtained from such an operation. - const DisableSave ds; - - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // override file read/write permissions. CAP_DAC_READ_SEARCH needs to be - // cleared for the same reason. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - // Create and open a file with read flag but without read permissions. - const std::string path = NewTempAbsPath(); - const FileDescriptor rfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_RDONLY, 0222)); - - EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); - const FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_WRONLY)); - - char c = 'x'; - EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - c = 0; - EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(c, 'x'); -} - -TEST(CreateTest, CreateWithWriteFlagNotAllowedByMode_NoRandomSave) { - // The only time we can open a file with flags forbidden by its permissions - // is when we are creating the file. We cannot re-open with the same flags, - // so we cannot restore an fd obtained from such an operation. - const DisableSave ds; - - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // override file read/write permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - // Create and open a file with write flag but without write permissions. - const std::string path = NewTempAbsPath(); - const FileDescriptor wfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_WRONLY, 0444)); - - EXPECT_THAT(open(path.c_str(), O_WRONLY), SyscallFailsWithErrno(EACCES)); - const FileDescriptor rfd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY)); - - char c = 'x'; - EXPECT_THAT(write(wfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - c = 0; - EXPECT_THAT(read(rfd.get(), &c, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ(c, 'x'); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc deleted file mode 100644 index 861617ff7..000000000 --- a/test/syscalls/linux/packet_socket.cc +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <linux/capability.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> -#include <net/ethernet.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/udp.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/base/internal/endian.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Some of these tests involve sending packets via AF_PACKET sockets and the -// loopback interface. Because AF_PACKET circumvents so much of the networking -// stack, Linux sees these packets as "martian", i.e. they claim to be to/from -// localhost but don't have the usual associated data. Thus Linux drops them by -// default. You can see where this happens by following the code at: -// -// - net/ipv4/ip_input.c:ip_rcv_finish, which calls -// - net/ipv4/route.c:ip_route_input_noref, which calls -// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian -// packets. -// -// To tell Linux not to drop these packets, you need to tell it to accept our -// funny packets (which are completely valid and correct, but lack associated -// in-kernel data because we use AF_PACKET): -// -// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local -// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet -// -// These tests require CAP_NET_RAW to run. - -// TODO(gvisor.dev/issue/173): gVisor support. - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -constexpr char kMessage[] = "soweoneul malhaebwa"; -constexpr in_port_t kPort = 0x409c; // htons(40000) - -// -// "Cooked" tests. Cooked AF_PACKET sockets do not contain link layer -// headers, and provide link layer destination/source information via a -// returned struct sockaddr_ll. -// - -// Send kMessage via sock to loopback -void SendUDPMessage(int sock) { - struct sockaddr_in dest = {}; - dest.sin_port = kPort; - dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dest.sin_family = AF_INET; - EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); -} - -// Send an IP packet and make sure ETH_P_<something else> doesn't pick it up. -TEST(BasicCookedPacketTest, WrongType) { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, ETH_P_PUP), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_PUP))); - - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Wait and make sure the socket never becomes readable. - struct pollfd pfd = {}; - pfd.fd = sock.get(); - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); -} - -// Tests for "cooked" (SOCK_DGRAM) packet(7) sockets. -class CookedPacketTest : public ::testing::TestWithParam<int> { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Gets the device index of the loopback device. - int GetLoopbackIndex(); - - // The socket used for both reading and writing. - int socket_; -}; - -void CookedPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - if (!IsRunningOnGvisor()) { - FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDONLY)); - FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDONLY)); - char enabled; - ASSERT_THAT(read(acceptLocal.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); - ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); - } - - ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), - SyscallSucceeds()); -} - -void CookedPacketTest::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(socket_), SyscallSucceeds()); - } -} - -int CookedPacketTest::GetLoopbackIndex() { - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - return ifr.ifr_ifindex; -} - -// Receive and verify the message via packet socket on interface. -void ReceiveMessage(int sock, int ifindex) { - // Wait for the socket to become readable. - struct pollfd pfd = {}; - pfd.fd = sock; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1)); - - // Read and verify the data. - constexpr size_t packet_size = - sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); - char buf[64]; - struct sockaddr_ll src = {}; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(sock, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(packet_size)); - - // sockaddr_ll ends with an 8 byte physical address field, but ethernet - // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 - // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns - // sizeof(sockaddr_ll). - ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); - - // TODO(gvisor.dev/issue/173): Verify protocol once we return it. - // Verify the source address. - EXPECT_EQ(src.sll_family, AF_PACKET); - EXPECT_EQ(src.sll_ifindex, ifindex); - EXPECT_EQ(src.sll_halen, ETH_ALEN); - EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); - // This came from the loopback device, so the address is all 0s. - for (int i = 0; i < src.sll_halen; i++) { - EXPECT_EQ(src.sll_addr[i], 0); - } - - // Verify the IP header. We memcpy to deal with pointer aligment. - struct iphdr ip = {}; - memcpy(&ip, buf, sizeof(ip)); - EXPECT_EQ(ip.ihl, 5); - EXPECT_EQ(ip.version, 4); - EXPECT_EQ(ip.tot_len, htons(packet_size)); - EXPECT_EQ(ip.protocol, IPPROTO_UDP); - EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK)); - EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK)); - - // Verify the UDP header. We memcpy to deal with pointer aligment. - struct udphdr udp = {}; - memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); - EXPECT_EQ(udp.dest, kPort); - EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); - - // Verify the payload. - char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); - EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); -} - -// Receive via a packet socket. -TEST_P(CookedPacketTest, Receive) { - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Receive and verify the data. - int loopback_index = GetLoopbackIndex(); - ReceiveMessage(socket_, loopback_index); -} - -// Send via a packet socket. -TEST_P(CookedPacketTest, Send) { - // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing. - SKIP_IF(IsRunningOnGvisor()); - - // Let's send a UDP packet and receive it using a regular UDP socket. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - struct sockaddr_in bind_addr = {}; - bind_addr.sin_family = AF_INET; - bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - bind_addr.sin_port = kPort; - ASSERT_THAT( - bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Set up the destination physical address. - struct sockaddr_ll dest = {}; - dest.sll_family = AF_PACKET; - dest.sll_halen = ETH_ALEN; - dest.sll_ifindex = GetLoopbackIndex(); - dest.sll_protocol = htons(ETH_P_IP); - // We're sending to the loopback device, so the address is all 0s. - memset(dest.sll_addr, 0x00, ETH_ALEN); - - // Set up the IP header. - struct iphdr iphdr = {0}; - iphdr.ihl = 5; - iphdr.version = 4; - iphdr.tos = 0; - iphdr.tot_len = - htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage)); - // Get a pseudo-random ID. If we clash with an in-use ID the test will fail, - // but we have no way of getting an ID we know to be good. - srand(*reinterpret_cast<unsigned int*>(&iphdr)); - iphdr.id = rand(); - // Linux sets this bit ("do not fragment") for small packets. - iphdr.frag_off = 1 << 6; - iphdr.ttl = 64; - iphdr.protocol = IPPROTO_UDP; - iphdr.daddr = htonl(INADDR_LOOPBACK); - iphdr.saddr = htonl(INADDR_LOOPBACK); - iphdr.check = IPChecksum(iphdr); - - // Set up the UDP header. - struct udphdr udphdr = {}; - udphdr.source = kPort; - udphdr.dest = kPort; - udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage)); - udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage)); - - // Copy both headers and the payload into our packet buffer. - char send_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)]; - memcpy(send_buf, &iphdr, sizeof(iphdr)); - memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr)); - memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); - - // Send it. - ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Wait for the packet to become available on both sockets. - struct pollfd pfd = {}; - pfd.fd = udp_sock.get(); - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - pfd.fd = socket_; - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - - // Receive on the packet socket. - char recv_buf[sizeof(send_buf)]; - ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0); - - // Receive on the UDP socket. - struct sockaddr_in src; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(sizeof(kMessage))); - // Check src and payload. - EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0); - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(src.sin_port, kPort); - EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK)); -} - -// Bind and receive via packet socket. -TEST_P(CookedPacketTest, BindReceive) { - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = GetLoopbackIndex(); - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Receive and verify the data. - ReceiveMessage(socket_, bind_addr.sll_ifindex); -} - -// Double Bind socket. -TEST_P(CookedPacketTest, DoubleBindSucceeds) { - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = GetLoopbackIndex(); - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Binding socket again should fail. - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - // Linux 4.09 returns EINVAL here, but some time before 4.19 it - // switched to EADDRINUSE. - SyscallSucceeds()); -} - -// Bind and verify we do not receive data on interface which is not bound -TEST_P(CookedPacketTest, BindDrop) { - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - // Get interface other than loopback. - struct ifreq ifr = {}; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (strcmp(i->ifa_name, "lo") != 0) { - strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); - break; - } - } - - // Skip if no interface is available other than loopback. - if (strlen(ifr.ifr_name) == 0) { - GTEST_SKIP(); - } - - // Get interface index. - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - - // Bind to packet socket requires only family, protocol and ifindex. - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = ifr.ifr_ifindex; - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Send to loopback interface. - struct sockaddr_in dest = {}; - dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dest.sin_family = AF_INET; - dest.sin_port = kPort; - EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Wait and make sure the socket never receives any data. - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); -} - -// Verify that we receive outbound packets. This test requires at least one -// non loopback interface so that we can actually capture an outgoing packet. -TEST_P(CookedPacketTest, ReceiveOutbound) { - // Only ETH_P_ALL sockets can receive outbound packets on linux. - SKIP_IF(GetParam() != ETH_P_ALL); - - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - // Get interface other than loopback. - struct ifreq ifr = {}; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (strcmp(i->ifa_name, "lo") != 0) { - strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); - break; - } - } - - // Skip if no interface is available other than loopback. - if (strlen(ifr.ifr_name) == 0) { - GTEST_SKIP(); - } - - // Get interface index and name. - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - int ifindex = ifr.ifr_ifindex; - - constexpr int kMACSize = 6; - char hwaddr[kMACSize]; - // Get interface address. - ASSERT_THAT(ioctl(socket_, SIOCGIFHWADDR, &ifr), SyscallSucceeds()); - ASSERT_THAT(ifr.ifr_hwaddr.sa_family, - AnyOf(Eq(ARPHRD_NONE), Eq(ARPHRD_ETHER))); - memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, kMACSize); - - // Just send it to the google dns server 8.8.8.8. It's UDP we don't care - // if it actually gets to the DNS Server we just want to see that we receive - // it on our AF_PACKET socket. - // - // NOTE: We just want to pick an IP that is non-local to avoid having to - // handle ARP as this should cause the UDP packet to be sent to the default - // gateway configured for the system under test. Otherwise the only packet we - // will see is the ARP query unless we picked an IP which will actually - // resolve. The test is a bit brittle but this was the best compromise for - // now. - struct sockaddr_in dest = {}; - ASSERT_EQ(inet_pton(AF_INET, "8.8.8.8", &dest.sin_addr.s_addr), 1); - dest.sin_family = AF_INET; - dest.sin_port = kPort; - EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Wait and make sure the socket receives the data. - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(1)); - - // Now read and check that the packet is the one we just sent. - // Read and verify the data. - constexpr size_t packet_size = - sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); - char buf[64]; - struct sockaddr_ll src = {}; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(packet_size)); - - // sockaddr_ll ends with an 8 byte physical address field, but ethernet - // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 - // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns - // sizeof(sockaddr_ll). - ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); - - // Verify the source address. - EXPECT_EQ(src.sll_family, AF_PACKET); - EXPECT_EQ(src.sll_ifindex, ifindex); - EXPECT_EQ(src.sll_halen, ETH_ALEN); - EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); - EXPECT_EQ(src.sll_pkttype, PACKET_OUTGOING); - // Verify the link address of the interface matches that of the non - // non loopback interface address we stored above. - for (int i = 0; i < src.sll_halen; i++) { - EXPECT_EQ(src.sll_addr[i], hwaddr[i]); - } - - // Verify the IP header. - struct iphdr ip = {}; - memcpy(&ip, buf, sizeof(ip)); - EXPECT_EQ(ip.ihl, 5); - EXPECT_EQ(ip.version, 4); - EXPECT_EQ(ip.tot_len, htons(packet_size)); - EXPECT_EQ(ip.protocol, IPPROTO_UDP); - EXPECT_EQ(ip.daddr, dest.sin_addr.s_addr); - EXPECT_NE(ip.saddr, htonl(INADDR_LOOPBACK)); - - // Verify the UDP header. - struct udphdr udp = {}; - memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); - EXPECT_EQ(udp.dest, kPort); - EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); - - // Verify the payload. - char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); - EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); -} - -// Bind with invalid address. -TEST_P(CookedPacketTest, BindFail) { - // Null address. - ASSERT_THAT( - bind(socket_, nullptr, sizeof(struct sockaddr)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallFailsWithErrno(EINVAL))); - - // Address of size 1. - uint8_t addr = 0; - ASSERT_THAT( - bind(socket_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EINVAL)); -} - -INSTANTIATE_TEST_SUITE_P(AllInetTests, CookedPacketTest, - ::testing::Values(ETH_P_IP, ETH_P_ALL)); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc deleted file mode 100644 index d25be0e30..000000000 --- a/test/syscalls/linux/packet_socket_raw.cc +++ /dev/null @@ -1,730 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/filter.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> -#include <net/ethernet.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/udp.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/internal/endian.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Some of these tests involve sending packets via AF_PACKET sockets and the -// loopback interface. Because AF_PACKET circumvents so much of the networking -// stack, Linux sees these packets as "martian", i.e. they claim to be to/from -// localhost but don't have the usual associated data. Thus Linux drops them by -// default. You can see where this happens by following the code at: -// -// - net/ipv4/ip_input.c:ip_rcv_finish, which calls -// - net/ipv4/route.c:ip_route_input_noref, which calls -// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian -// packets. -// -// To tell Linux not to drop these packets, you need to tell it to accept our -// funny packets (which are completely valid and correct, but lack associated -// in-kernel data because we use AF_PACKET): -// -// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local -// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet -// -// These tests require CAP_NET_RAW to run. - -// TODO(gvisor.dev/issue/173): gVisor support. - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -constexpr char kMessage[] = "soweoneul malhaebwa"; -constexpr in_port_t kPort = 0x409c; // htons(40000) - -// Send kMessage via sock to loopback -void SendUDPMessage(int sock) { - struct sockaddr_in dest = {}; - dest.sin_port = kPort; - dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dest.sin_family = AF_INET; - EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); -} - -// -// Raw tests. Packets sent with raw AF_PACKET sockets always include link layer -// headers. -// - -// Tests for "raw" (SOCK_RAW) packet(7) sockets. -class RawPacketTest : public ::testing::TestWithParam<int> { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Gets the device index of the loopback device. - int GetLoopbackIndex(); - - // The socket used for both reading and writing. - int s_; -}; - -void RawPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, htons(GetParam())), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - if (!IsRunningOnGvisor()) { - // Ensure that looped back packets aren't rejected by the kernel. - FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDWR)); - FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDWR)); - char enabled; - ASSERT_THAT(read(acceptLocal.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - if (enabled != '1') { - enabled = '1'; - ASSERT_THAT(lseek(acceptLocal.get(), 0, SEEK_SET), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(write(acceptLocal.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_THAT(lseek(acceptLocal.get(), 0, SEEK_SET), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(read(acceptLocal.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); - } - - ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - if (enabled != '1') { - enabled = '1'; - ASSERT_THAT(lseek(routeLocalnet.get(), 0, SEEK_SET), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(write(routeLocalnet.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_THAT(lseek(routeLocalnet.get(), 0, SEEK_SET), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); - } - } - - ASSERT_THAT(s_ = socket(AF_PACKET, SOCK_RAW, htons(GetParam())), - SyscallSucceeds()); -} - -void RawPacketTest::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(s_), SyscallSucceeds()); - } -} - -int RawPacketTest::GetLoopbackIndex() { - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - EXPECT_THAT(ioctl(s_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - return ifr.ifr_ifindex; -} - -// Receive via a packet socket. -TEST_P(RawPacketTest, Receive) { - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Wait for the socket to become readable. - struct pollfd pfd = {}; - pfd.fd = s_; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1)); - - // Read and verify the data. - constexpr size_t packet_size = sizeof(struct ethhdr) + sizeof(struct iphdr) + - sizeof(struct udphdr) + sizeof(kMessage); - char buf[64]; - struct sockaddr_ll src = {}; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(s_, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(packet_size)); - // sockaddr_ll ends with an 8 byte physical address field, but ethernet - // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 - // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns - // sizeof(sockaddr_ll). - ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); - - // TODO(gvisor.dev/issue/173): Verify protocol once we return it. - // Verify the source address. - EXPECT_EQ(src.sll_family, AF_PACKET); - EXPECT_EQ(src.sll_ifindex, GetLoopbackIndex()); - EXPECT_EQ(src.sll_halen, ETH_ALEN); - EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); - // This came from the loopback device, so the address is all 0s. - for (int i = 0; i < src.sll_halen; i++) { - EXPECT_EQ(src.sll_addr[i], 0); - } - - // Verify the ethernet header. We memcpy to deal with pointer alignment. - struct ethhdr eth = {}; - memcpy(ð, buf, sizeof(eth)); - // The destination and source address should be 0, for loopback. - for (int i = 0; i < ETH_ALEN; i++) { - EXPECT_EQ(eth.h_dest[i], 0); - EXPECT_EQ(eth.h_source[i], 0); - } - EXPECT_EQ(eth.h_proto, htons(ETH_P_IP)); - - // Verify the IP header. We memcpy to deal with pointer aligment. - struct iphdr ip = {}; - memcpy(&ip, buf + sizeof(ethhdr), sizeof(ip)); - EXPECT_EQ(ip.ihl, 5); - EXPECT_EQ(ip.version, 4); - EXPECT_EQ(ip.tot_len, htons(packet_size - sizeof(eth))); - EXPECT_EQ(ip.protocol, IPPROTO_UDP); - EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK)); - EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK)); - - // Verify the UDP header. We memcpy to deal with pointer aligment. - struct udphdr udp = {}; - memcpy(&udp, buf + sizeof(eth) + sizeof(iphdr), sizeof(udp)); - EXPECT_EQ(udp.dest, kPort); - EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); - - // Verify the payload. - char* payload = reinterpret_cast<char*>(buf + sizeof(eth) + sizeof(iphdr) + - sizeof(udphdr)); - EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); -} - -// Send via a packet socket. -TEST_P(RawPacketTest, Send) { - // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing. - SKIP_IF(IsRunningOnGvisor()); - - // Let's send a UDP packet and receive it using a regular UDP socket. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - struct sockaddr_in bind_addr = {}; - bind_addr.sin_family = AF_INET; - bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - bind_addr.sin_port = kPort; - ASSERT_THAT( - bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Set up the destination physical address. - struct sockaddr_ll dest = {}; - dest.sll_family = AF_PACKET; - dest.sll_halen = ETH_ALEN; - dest.sll_ifindex = GetLoopbackIndex(); - dest.sll_protocol = htons(ETH_P_IP); - // We're sending to the loopback device, so the address is all 0s. - memset(dest.sll_addr, 0x00, ETH_ALEN); - - // Set up the ethernet header. The kernel takes care of the footer. - // We're sending to and from hardware address 0 (loopback). - struct ethhdr eth = {}; - eth.h_proto = htons(ETH_P_IP); - - // Set up the IP header. - struct iphdr iphdr = {}; - iphdr.ihl = 5; - iphdr.version = 4; - iphdr.tos = 0; - iphdr.tot_len = - htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage)); - // Get a pseudo-random ID. If we clash with an in-use ID the test will fail, - // but we have no way of getting an ID we know to be good. - srand(*reinterpret_cast<unsigned int*>(&iphdr)); - iphdr.id = rand(); - // Linux sets this bit ("do not fragment") for small packets. - iphdr.frag_off = 1 << 6; - iphdr.ttl = 64; - iphdr.protocol = IPPROTO_UDP; - iphdr.daddr = htonl(INADDR_LOOPBACK); - iphdr.saddr = htonl(INADDR_LOOPBACK); - iphdr.check = IPChecksum(iphdr); - - // Set up the UDP header. - struct udphdr udphdr = {}; - udphdr.source = kPort; - udphdr.dest = kPort; - udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage)); - udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage)); - - // Copy both headers and the payload into our packet buffer. - char - send_buf[sizeof(eth) + sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)]; - memcpy(send_buf, ð, sizeof(eth)); - memcpy(send_buf + sizeof(ethhdr), &iphdr, sizeof(iphdr)); - memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr), &udphdr, sizeof(udphdr)); - memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr), kMessage, - sizeof(kMessage)); - - // Send it. - ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Wait for the packet to become available on both sockets. - struct pollfd pfd = {}; - pfd.fd = udp_sock.get(); - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - pfd.fd = s_; - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - - // Receive on the packet socket. - char recv_buf[sizeof(send_buf)]; - ASSERT_THAT(recv(s_, recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0); - - // Receive on the UDP socket. - struct sockaddr_in src; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(sizeof(kMessage))); - // Check src and payload. - EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0); - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(src.sin_port, kPort); - EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK)); -} - -// Check that setting SO_RCVBUF below min is clamped to the minimum -// receive buffer size. -TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover minimum receive buf size by trying to set it to zero. - // See: - // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820 - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &below_min, sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_RCVBUF above max is clamped to the maximum -// receive buffer size. -TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover max buf size by trying to set the largest possible buffer size. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &above_max, sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. -TEST_P(RawPacketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int max = 0; - int min = 0; - { - // Discover max buf size by trying to set a really large buffer size. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by trying to set a zero size receive buffer - // size. - // See: - // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820 - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &quarter_sz, sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - // TODO(gvisor.dev/issue/2926): Remove when Netstack matches linux behavior. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - ASSERT_EQ(quarter_sz, val); -} - -// Check that setting SO_SNDBUF below min is clamped to the minimum -// receive buffer size. -TEST_P(RawPacketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover minimum buffer size by trying to set it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &below_min, sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_SNDBUF above max is clamped to the maximum -// send buffer size. -TEST_P(RawPacketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover maximum buffer size by trying to set it to a large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &above_max, sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. -TEST_P(RawPacketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int max = 0; - int min = 0; - { - // Discover maximum buffer size by trying to set it to a large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by trying to set it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &quarter_sz, sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - quarter_sz *= 2; - ASSERT_EQ(quarter_sz, val); -} - -TEST_P(RawPacketTest, GetSocketError) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(val, 0); -} - -TEST_P(RawPacketTest, GetSocketErrorBind) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - { - // Bind to the loopback device. - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = GetLoopbackIndex(); - - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // SO_ERROR should return no errors. - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(val, 0); - } - - { - // Now try binding to an invalid interface. - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = 0xffff; // Just pick a really large number. - - // Binding should fail with EINVAL - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallFailsWithErrno(ENODEV)); - - // SO_ERROR does not return error when the device is invalid. - // On Linux there is just one odd ball condition where this can return - // an error where the device was valid and then removed or disabled - // between the first check for index and the actual registration of - // the packet endpoint. On Netstack this is not possible as the stack - // global mutex is held during registration and check. - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(val, 0); - } -} - -TEST_P(RawPacketTest, SetSocketDetachFilterNoInstalledFilter) { - // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. - // - // gVisor returns no error on SO_DETACH_FILTER even if there is no filter - // attached unlike linux which does return ENOENT in such cases. This is - // because gVisor doesn't support SO_ATTACH_FILTER and just silently returns - // success. - if (IsRunningOnGvisor()) { - constexpr int val = 0; - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallSucceeds()); - return; - } - constexpr int val = 0; - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(RawPacketTest, GetSocketDetachFilter) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len), - SyscallFailsWithErrno(ENOPROTOOPT)); -} - -TEST_P(RawPacketTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int level = SOL_SOCKET; - int type = SO_LINGER; - - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT(setsockopt(s_, level, type, &sl, sizeof(sl)), - SyscallSucceedsWithValue(0)); - - struct linger got_linger = {}; - socklen_t length = sizeof(sl); - ASSERT_THAT(getsockopt(s_, level, type, &got_linger, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); -} - -TEST_P(RawPacketTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} -INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest, - ::testing::Values(ETH_P_IP, ETH_P_ALL)); - -class RawPacketMsgSizeTest : public ::testing::TestWithParam<TestAddress> {}; - -TEST_P(RawPacketMsgSizeTest, SendTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - TestAddress addr = GetParam().WithPort(kPort); - - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(addr.family(), SOCK_RAW, IPPROTO_UDP)); - - ASSERT_THAT( - connect(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - const char buf[65536] = {}; - ASSERT_THAT(send(udp_sock.get(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EMSGSIZE)); -} - -TEST_P(RawPacketMsgSizeTest, SpliceTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - const char buf[65536] = {}; - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - ASSERT_THAT(write(fds[1], buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - TestAddress addr = GetParam().WithPort(kPort); - - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(addr.family(), SOCK_RAW, IPPROTO_UDP)); - - ASSERT_THAT( - connect(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - ssize_t n = splice(fds[0], nullptr, udp_sock.get(), nullptr, sizeof(buf), 0); - if (IsRunningOnGvisor()) { - EXPECT_THAT(n, SyscallFailsWithErrno(EMSGSIZE)); - } else { - // TODO(gvisor.dev/issue/138): Linux sends out multiple UDP datagrams, each - // of the size of a page. - EXPECT_THAT(n, SyscallSucceedsWithValue(sizeof(buf))); - } -} - -INSTANTIATE_TEST_SUITE_P(AllRawPacketMsgSizeTest, RawPacketMsgSizeTest, - ::testing::Values(V4Loopback(), V6Loopback())); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc deleted file mode 100644 index 13afa0eaf..000000000 --- a/test/syscalls/linux/partial_bad_buffer.cc +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <sys/mman.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::Gt; - -namespace gvisor { -namespace testing { - -namespace { - -constexpr char kMessage[] = "hello world"; - -// PartialBadBufferTest checks the result of various IO syscalls when passed a -// buffer that does not have the space specified in the syscall (most of it is -// PROT_NONE). Linux is annoyingly inconsistent among different syscalls, so we -// test all of them. -class PartialBadBufferTest : public ::testing::Test { - protected: - void SetUp() override { - // Create and open a directory for getdents cases. - directory_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT( - directory_fd_ = open(directory_.path().c_str(), O_RDONLY | O_DIRECTORY), - SyscallSucceeds()); - - // Create and open a normal file, placing it in the directory - // so the getdents cases have some dirents. - name_ = JoinPath(directory_.path(), "a"); - ASSERT_THAT(fd_ = open(name_.c_str(), O_RDWR | O_CREAT, 0644), - SyscallSucceeds()); - - // Write some initial data. - size_t size = sizeof(kMessage) - 1; - EXPECT_THAT(WriteFd(fd_, &kMessage, size), SyscallSucceedsWithValue(size)); - ASSERT_THAT(lseek(fd_, 0, SEEK_SET), SyscallSucceeds()); - - // Map a useable buffer. - addr_ = mmap(0, 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - ASSERT_NE(addr_, MAP_FAILED); - char* buf = reinterpret_cast<char*>(addr_); - - // Guard page for our read to run into. - ASSERT_THAT(mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, - PROT_NONE), - SyscallSucceeds()); - - // Leave only one free byte in the buffer. - bad_buffer_ = buf + kPageSize - 1; - } - - off_t Size() { - struct stat st; - int rc = fstat(fd_, &st); - if (rc < 0) { - return static_cast<off_t>(rc); - } - return st.st_size; - } - - void TearDown() override { - EXPECT_THAT(munmap(addr_, 2 * kPageSize), SyscallSucceeds()) << addr_; - EXPECT_THAT(close(fd_), SyscallSucceeds()); - EXPECT_THAT(unlink(name_.c_str()), SyscallSucceeds()); - EXPECT_THAT(close(directory_fd_), SyscallSucceeds()); - } - - // Return buffer with n bytes of free space. - // N.B. this is the same buffer used to back bad_buffer_. - char* FreeBytes(size_t n) { - TEST_CHECK(n <= static_cast<size_t>(4096)); - return reinterpret_cast<char*>(addr_) + kPageSize - n; - } - - std::string name_; - int fd_; - TempPath directory_; - int directory_fd_; - void* addr_; - char* bad_buffer_; -}; - -// We do both "big" and "small" tests to try to hit the "zero copy" and -// non-"zero copy" paths, which have different code paths for handling faults. - -TEST_F(PartialBadBufferTest, ReadBig) { - EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, kPageSize), - SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, ReadSmall) { - EXPECT_THAT(RetryEINTR(read)(fd_, bad_buffer_, 10), - SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, PreadBig) { - EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, kPageSize, 0), - SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, PreadSmall) { - EXPECT_THAT(RetryEINTR(pread)(fd_, bad_buffer_, 10, 0), - SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, ReadvBig) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = kPageSize; - - EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, ReadvSmall) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = 10; - - EXPECT_THAT(RetryEINTR(readv)(fd_, &vec, 1), SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, PreadvBig) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = kPageSize; - - EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, PreadvSmall) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = 10; - - EXPECT_THAT(RetryEINTR(preadv)(fd_, &vec, 1, 0), SyscallSucceedsWithValue(1)); - EXPECT_EQ('h', bad_buffer_[0]); -} - -TEST_F(PartialBadBufferTest, WriteBig) { - off_t orig_size = Size(); - int n; - - ASSERT_THAT(lseek(fd_, orig_size, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT( - (n = RetryEINTR(write)(fd_, bad_buffer_, kPageSize)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, WriteSmall) { - off_t orig_size = Size(); - int n; - - ASSERT_THAT(lseek(fd_, orig_size, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT( - (n = RetryEINTR(write)(fd_, bad_buffer_, 10)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, PwriteBig) { - off_t orig_size = Size(); - int n; - - EXPECT_THAT( - (n = RetryEINTR(pwrite)(fd_, bad_buffer_, kPageSize, orig_size)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, PwriteSmall) { - off_t orig_size = Size(); - int n; - - EXPECT_THAT( - (n = RetryEINTR(pwrite)(fd_, bad_buffer_, 10, orig_size)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, WritevBig) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = kPageSize; - off_t orig_size = Size(); - int n; - - ASSERT_THAT(lseek(fd_, orig_size, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT( - (n = RetryEINTR(writev)(fd_, &vec, 1)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, WritevSmall) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = 10; - off_t orig_size = Size(); - int n; - - ASSERT_THAT(lseek(fd_, orig_size, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT( - (n = RetryEINTR(writev)(fd_, &vec, 1)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, PwritevBig) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = kPageSize; - off_t orig_size = Size(); - int n; - - EXPECT_THAT( - (n = RetryEINTR(pwritev)(fd_, &vec, 1, orig_size)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -TEST_F(PartialBadBufferTest, PwritevSmall) { - struct iovec vec; - vec.iov_base = bad_buffer_; - vec.iov_len = 10; - off_t orig_size = Size(); - int n; - - EXPECT_THAT( - (n = RetryEINTR(pwritev)(fd_, &vec, 1, orig_size)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(1))); - EXPECT_EQ(Size(), orig_size + (n >= 0 ? n : 0)); -} - -// getdents returns EFAULT when the you claim the buffer is large enough, but -// it actually isn't. -TEST_F(PartialBadBufferTest, GetdentsBig) { - EXPECT_THAT(RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_, - kPageSize), - SyscallFailsWithErrno(EFAULT)); -} - -// getdents returns EINVAL when the you claim the buffer is too small. -TEST_F(PartialBadBufferTest, GetdentsSmall) { - EXPECT_THAT( - RetryEINTR(syscall)(SYS_getdents64, directory_fd_, bad_buffer_, 10), - SyscallFailsWithErrno(EINVAL)); -} - -// getdents will write entries into a buffer if there is space before it faults. -TEST_F(PartialBadBufferTest, GetdentsOneEntry) { - // 30 bytes is enough for one (small) entry. - char* buf = FreeBytes(30); - - EXPECT_THAT( - RetryEINTR(syscall)(SYS_getdents64, directory_fd_, buf, kPageSize), - SyscallSucceedsWithValue(Gt(0))); -} - -PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - addr.ss_family = family; - switch (family) { - case AF_INET: - reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr = - htonl(INADDR_LOOPBACK); - break; - case AF_INET6: - reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr = - in6addr_loopback; - break; - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } - return addr; -} - -// SendMsgTCP verifies that calling sendmsg with a bad address returns an -// EFAULT. It also verifies that passing a buffer which is made up of 2 -// pages one valid and one guard page succeeds as long as the write is -// for exactly the size of 1 page. -TEST_F(PartialBadBufferTest, SendMsgTCP_NoRandomSave) { - // FIXME(b/171436815): Netstack save/restore is broken. - const DisableSave ds; - - auto listen_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET)); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT(bind(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the address we're listening on, then connect to it. We need to do this - // because we're allowing the stack to pick a port for us. - ASSERT_THAT(getsockname(listen_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - auto send_socket = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT( - RetryEINTR(connect)(send_socket.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Accept the connection. - auto recv_socket = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr)); - - // TODO(gvisor.dev/issue/674): Update this once Netstack matches linux - // behaviour on a setsockopt of SO_RCVBUF/SO_SNDBUF. - // - // Set SO_SNDBUF for socket to exactly kPageSize+1. - // - // gVisor does not double the value passed in SO_SNDBUF like linux does so we - // just increase it by 1 byte here for gVisor so that we can test writing 1 - // byte past the valid page and check that it triggers an EFAULT - // correctly. Otherwise in gVisor the sendmsg call will just return with no - // error with kPageSize bytes written successfully. - const uint32_t buf_size = kPageSize + 1; - ASSERT_THAT(setsockopt(send_socket.get(), SOL_SOCKET, SO_SNDBUF, &buf_size, - sizeof(buf_size)), - SyscallSucceedsWithValue(0)); - - struct msghdr hdr = {}; - struct iovec iov = {}; - iov.iov_base = bad_buffer_; - iov.iov_len = kPageSize; - hdr.msg_iov = &iov; - hdr.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0), - SyscallFailsWithErrno(EFAULT)); - - // Now assert that writing kPageSize from addr_ succeeds. - iov.iov_base = addr_; - ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0), - SyscallSucceedsWithValue(kPageSize)); - // Read all the data out so that we drain the socket SND_BUF on the sender. - std::vector<char> buffer(kPageSize); - ASSERT_THAT(RetryEINTR(read)(recv_socket.get(), buffer.data(), kPageSize), - SyscallSucceedsWithValue(kPageSize)); - - // Sleep for a shortwhile to ensure that we have time to process the - // ACKs. This is not strictly required unless running under gotsan which is a - // lot slower and can result in the next write to write only 1 byte instead of - // our intended kPageSize + 1. - absl::SleepFor(absl::Milliseconds(50)); - - // Now assert that writing > kPageSize results in EFAULT. - iov.iov_len = kPageSize + 1; - ASSERT_THAT(RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0), - SyscallFailsWithErrno(EFAULT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pause.cc b/test/syscalls/linux/pause.cc deleted file mode 100644 index 8c05efd6f..000000000 --- a/test/syscalls/linux/pause.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <signal.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <atomic> - -#include "gtest/gtest.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -void NoopSignalHandler(int sig, siginfo_t* info, void* context) {} - -} // namespace - -TEST(PauseTest, OnlyReturnsWhenSignalHandled) { - struct sigaction sa; - sigfillset(&sa.sa_mask); - - // Ensure that SIGUSR1 is ignored. - sa.sa_handler = SIG_IGN; - ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); - - // Register a handler for SIGUSR2. - sa.sa_sigaction = NoopSignalHandler; - sa.sa_flags = SA_SIGINFO; - ASSERT_THAT(sigaction(SIGUSR2, &sa, nullptr), SyscallSucceeds()); - - // The child sets their own tid. - absl::Mutex mu; - pid_t child_tid = 0; - bool child_tid_available = false; - std::atomic<int> sent_signal{0}; - std::atomic<int> waking_signal{0}; - ScopedThread t([&] { - mu.Lock(); - child_tid = gettid(); - child_tid_available = true; - mu.Unlock(); - EXPECT_THAT(pause(), SyscallFailsWithErrno(EINTR)); - waking_signal.store(sent_signal.load()); - }); - mu.Lock(); - mu.Await(absl::Condition(&child_tid_available)); - mu.Unlock(); - - // Wait a bit to let the child enter pause(). - absl::SleepFor(absl::Seconds(3)); - - // The child should not be woken by SIGUSR1. - sent_signal.store(SIGUSR1); - ASSERT_THAT(tgkill(getpid(), child_tid, SIGUSR1), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(3)); - - // The child should be woken by SIGUSR2. - sent_signal.store(SIGUSR2); - ASSERT_THAT(tgkill(getpid(), child_tid, SIGUSR2), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(3)); - - EXPECT_EQ(SIGUSR2, waking_signal.load()); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc deleted file mode 100644 index 999c8ab6b..000000000 --- a/test/syscalls/linux/ping_socket.cc +++ /dev/null @@ -1,76 +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. - -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -// Test ICMP port exhaustion returns EAGAIN. -// -// We disable both random/cooperative S/R for this test as it makes way too many -// syscalls. -TEST(PingSocket, ICMPPortExhaustion_NoRandomSave) { - DisableSave ds; - - { - auto s = Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); - if (!s.ok()) { - ASSERT_EQ(s.error().errno_value(), EACCES); - GTEST_SKIP(); - } - } - - const struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_addr = - { - .s_addr = htonl(INADDR_LOOPBACK), - }, - }; - - std::vector<FileDescriptor> sockets; - constexpr int kSockets = 65536; - for (int i = 0; i < kSockets; i++) { - auto s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); - int ret = connect(s.get(), reinterpret_cast<const struct sockaddr*>(&addr), - sizeof(addr)); - if (ret == 0) { - sockets.push_back(std::move(s)); - continue; - } - ASSERT_THAT(ret, SyscallFailsWithErrno(EAGAIN)); - break; - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc deleted file mode 100644 index 01ccbdcd2..000000000 --- a/test/syscalls/linux/pipe.cc +++ /dev/null @@ -1,690 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> /* Obtain O_* constant definitions */ -#include <linux/magic.h> -#include <sys/ioctl.h> -#include <sys/statfs.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/synchronization/notification.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Used as a non-zero sentinel value, below. -constexpr int kTestValue = 0x12345678; - -// Used for synchronization in race tests. -const absl::Duration syncDelay = absl::Seconds(2); - -struct PipeCreator { - std::string name_; - - // void (fds, is_blocking, is_namedpipe). - std::function<void(int[2], bool*, bool*)> create_; -}; - -class PipeTest : public ::testing::TestWithParam<PipeCreator> { - public: - static void SetUpTestSuite() { - // Tests intentionally generate SIGPIPE. - TEST_PCHECK(signal(SIGPIPE, SIG_IGN) != SIG_ERR); - } - - // Initializes rfd_ and wfd_ as a blocking pipe. - // - // The return value indicates success: the test should be skipped otherwise. - bool CreateBlocking() { return create(true); } - - // Initializes rfd_ and wfd_ as a non-blocking pipe. - // - // The return value is per CreateBlocking. - bool CreateNonBlocking() { return create(false); } - - // Returns true iff the pipe represents a named pipe. - bool IsNamedPipe() const { return named_pipe_; } - - 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 static_cast<size_t>(s1); - } - - static void TearDownTestSuite() { - TEST_PCHECK(signal(SIGPIPE, SIG_DFL) != SIG_ERR); - } - - private: - bool create(bool wants_blocking) { - // Generate the pipe. - int fds[2] = {-1, -1}; - bool is_blocking = false; - GetParam().create_(fds, &is_blocking, &named_pipe_); - if (fds[0] < 0 || fds[1] < 0) { - return false; - } - - // Save descriptors. - rfd_.reset(fds[0]); - wfd_.reset(fds[1]); - - // Adjust blocking, if needed. - if (!is_blocking && wants_blocking) { - // Clear the blocking flag. - EXPECT_THAT(fcntl(fds[0], F_SETFL, 0), SyscallSucceeds()); - EXPECT_THAT(fcntl(fds[1], F_SETFL, 0), SyscallSucceeds()); - } else if (is_blocking && !wants_blocking) { - // Set the descriptors to blocking. - EXPECT_THAT(fcntl(fds[0], F_SETFL, O_NONBLOCK), SyscallSucceeds()); - EXPECT_THAT(fcntl(fds[1], F_SETFL, O_NONBLOCK), SyscallSucceeds()); - } - - return true; - } - - protected: - FileDescriptor rfd_; - FileDescriptor wfd_; - - private: - bool named_pipe_ = false; -}; - -TEST_P(PipeTest, Inode) { - SKIP_IF(!CreateBlocking()); - - // Ensure that the inode number is the same for each end. - struct stat rst; - ASSERT_THAT(fstat(rfd_.get(), &rst), SyscallSucceeds()); - struct stat wst; - ASSERT_THAT(fstat(wfd_.get(), &wst), SyscallSucceeds()); - EXPECT_EQ(rst.st_ino, wst.st_ino); -} - -TEST_P(PipeTest, Permissions) { - SKIP_IF(!CreateBlocking()); - - // Attempt bad operations. - int buf = kTestValue; - ASSERT_THAT(write(rfd_.get(), &buf, sizeof(buf)), - SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(read(wfd_.get(), &buf, sizeof(buf)), - SyscallFailsWithErrno(EBADF)); -} - -TEST_P(PipeTest, Flags) { - SKIP_IF(!CreateBlocking()); - - if (IsNamedPipe()) { - // May be stubbed to zero; define locally. - EXPECT_THAT(fcntl(rfd_.get(), F_GETFL), - SyscallSucceedsWithValue(kOLargeFile | O_RDONLY)); - EXPECT_THAT(fcntl(wfd_.get(), F_GETFL), - SyscallSucceedsWithValue(kOLargeFile | O_WRONLY)); - } else { - EXPECT_THAT(fcntl(rfd_.get(), F_GETFL), SyscallSucceedsWithValue(O_RDONLY)); - EXPECT_THAT(fcntl(wfd_.get(), F_GETFL), SyscallSucceedsWithValue(O_WRONLY)); - } -} - -TEST_P(PipeTest, Write) { - SKIP_IF(!CreateBlocking()); - - int wbuf = kTestValue; - int rbuf = ~kTestValue; - ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)), - SyscallSucceedsWithValue(sizeof(wbuf))); - ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(wbuf, rbuf); -} - -TEST_P(PipeTest, WritePage) { - SKIP_IF(!CreateBlocking()); - - std::vector<char> wbuf(kPageSize); - RandomizeBuffer(wbuf.data(), wbuf.size()); - std::vector<char> rbuf(wbuf.size()); - - ASSERT_THAT(write(wfd_.get(), wbuf.data(), wbuf.size()), - SyscallSucceedsWithValue(wbuf.size())); - ASSERT_THAT(read(rfd_.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), wbuf.size()), 0); -} - -TEST_P(PipeTest, NonBlocking) { - SKIP_IF(!CreateNonBlocking()); - - int wbuf = kTestValue; - int rbuf = ~kTestValue; - EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallFailsWithErrno(EWOULDBLOCK)); - ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)), - SyscallSucceedsWithValue(sizeof(wbuf))); - - ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(wbuf, rbuf); - EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST(PipeTest, StatFS) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - struct statfs st; - EXPECT_THAT(fstatfs(fds[0], &st), SyscallSucceeds()); - EXPECT_EQ(st.f_type, PIPEFS_MAGIC); - EXPECT_EQ(st.f_bsize, getpagesize()); - EXPECT_EQ(st.f_namelen, NAME_MAX); -} - -TEST(Pipe2Test, CloExec) { - int fds[2]; - ASSERT_THAT(pipe2(fds, O_CLOEXEC), SyscallSucceeds()); - EXPECT_THAT(fcntl(fds[0], F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - EXPECT_THAT(fcntl(fds[1], F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); - EXPECT_THAT(close(fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(fds[1]), SyscallSucceeds()); -} - -TEST(Pipe2Test, BadOptions) { - int fds[2]; - EXPECT_THAT(pipe2(fds, 0xDEAD), SyscallFailsWithErrno(EINVAL)); -} - -// Tests that opening named pipes with O_TRUNC shouldn't cause an error, but -// calls to (f)truncate should. -TEST(NamedPipeTest, Truncate) { - const std::string tmp_path = NewTempAbsPath(); - SKIP_IF(mkfifo(tmp_path.c_str(), 0644) != 0); - - ASSERT_THAT(open(tmp_path.c_str(), O_NONBLOCK | O_RDONLY), SyscallSucceeds()); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(tmp_path.c_str(), O_RDWR | O_NONBLOCK | O_TRUNC)); - - ASSERT_THAT(truncate(tmp_path.c_str(), 0), SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(PipeTest, Seek) { - SKIP_IF(!CreateBlocking()); - - for (int i = 0; i < 4; i++) { - // Attempt absolute seeks. - EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(rfd_.get(), 4, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), 4, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); - - // Attempt relative seeks. - EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(rfd_.get(), 4, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), 4, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - - // Attempt end-of-file seeks. - EXPECT_THAT(lseek(rfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(rfd_.get(), -4, SEEK_END), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), 0, SEEK_CUR), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(lseek(wfd_.get(), -4, SEEK_END), SyscallFailsWithErrno(ESPIPE)); - - // Add some more data to the pipe. - int buf = kTestValue; - ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - } -} - -TEST_P(PipeTest, OffsetCalls) { - SKIP_IF(!CreateBlocking()); - - int buf; - EXPECT_THAT(pread(wfd_.get(), &buf, sizeof(buf), 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(pwrite(rfd_.get(), &buf, sizeof(buf), 0), - SyscallFailsWithErrno(ESPIPE)); - - struct iovec iov; - iov.iov_base = &buf; - iov.iov_len = sizeof(buf); - EXPECT_THAT(preadv(wfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(pwritev(rfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE)); -} - -TEST_P(PipeTest, WriterSideCloses) { - SKIP_IF(!CreateBlocking()); - - ScopedThread t([this]() { - int buf = ~kTestValue; - ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - EXPECT_EQ(buf, kTestValue); - // This will return when the close() completes. - ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)), SyscallSucceeds()); - // This will return straight away. - ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); - }); - - // Sleep a bit so the thread can block. - absl::SleepFor(syncDelay); - - // Write to unblock. - int buf = kTestValue; - ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Sleep a bit so the thread can block again. - absl::SleepFor(syncDelay); - - // Allow the thread to complete. - ASSERT_THAT(close(wfd_.release()), SyscallSucceeds()); - t.Join(); -} - -TEST_P(PipeTest, WriterSideClosesReadDataFirst) { - SKIP_IF(!CreateBlocking()); - - int wbuf = kTestValue; - ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)), - SyscallSucceedsWithValue(sizeof(wbuf))); - ASSERT_THAT(close(wfd_.release()), SyscallSucceeds()); - - int rbuf; - ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(wbuf, rbuf); - EXPECT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(PipeTest, ReaderSideCloses) { - SKIP_IF(!CreateBlocking()); - - ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - int buf = kTestValue; - EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(PipeTest, CloseTwice) { - SKIP_IF(!CreateBlocking()); - - int reader = rfd_.release(); - int writer = wfd_.release(); - ASSERT_THAT(close(reader), SyscallSucceeds()); - ASSERT_THAT(close(writer), SyscallSucceeds()); - EXPECT_THAT(close(reader), SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(close(writer), SyscallFailsWithErrno(EBADF)); -} - -// Blocking write returns EPIPE when read end is closed if nothing has been -// written. -TEST_P(PipeTest, BlockWriteClosed) { - SKIP_IF(!CreateBlocking()); - - absl::Notification notify; - ScopedThread t([this, ¬ify]() { - std::vector<char> buf(Size()); - // Exactly fill the pipe buffer. - ASSERT_THAT(WriteFd(wfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - notify.Notify(); - - // Attempt to write one more byte. Blocks. - // N.B. Don't use WriteFd, we don't want a retry. - EXPECT_THAT(write(wfd_.get(), buf.data(), 1), SyscallFailsWithErrno(EPIPE)); - }); - - notify.WaitForNotification(); - ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - t.Join(); -} - -// Blocking write returns EPIPE when read end is closed even if something has -// been written. -TEST_P(PipeTest, BlockPartialWriteClosed) { - SKIP_IF(!CreateBlocking()); - - ScopedThread t([this]() { - const int pipe_size = Size(); - std::vector<char> buf(2 * pipe_size); - - // Write more than fits in the buffer. Blocks then returns partial write - // when the other end is closed. The next call returns EPIPE. - ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(pipe_size)); - EXPECT_THAT(write(wfd_.get(), buf.data(), buf.size()), - SyscallFailsWithErrno(EPIPE)); - }); - - // Leave time for write to become blocked. - absl::SleepFor(syncDelay); - - // Unblock the above. - ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - t.Join(); -} - -TEST_P(PipeTest, ReadFromClosedFd_NoRandomSave) { - SKIP_IF(!CreateBlocking()); - - absl::Notification notify; - ScopedThread t([this, ¬ify]() { - notify.Notify(); - int buf; - ASSERT_THAT(read(rfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_EQ(kTestValue, buf); - }); - notify.WaitForNotification(); - - // Make sure that the thread gets to read(). - absl::SleepFor(syncDelay); - - { - // We cannot save/restore here as the read end of pipe is closed but there - // is ongoing read() above. We will not be able to restart the read() - // successfully in restore run since the read fd is closed. - const DisableSave ds; - ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - int buf = kTestValue; - ASSERT_THAT(write(wfd_.get(), &buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - t.Join(); - } -} - -TEST_P(PipeTest, FionRead) { - SKIP_IF(!CreateBlocking()); - - int n; - ASSERT_THAT(ioctl(rfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - ASSERT_THAT(ioctl(wfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - std::vector<char> buf(Size()); - ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_THAT(ioctl(rfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, buf.size()); - EXPECT_THAT(ioctl(wfd_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, buf.size()); -} - -// Test that opening an empty anonymous pipe RDONLY via /proc/self/fd/N does not -// block waiting for a writer. -TEST_P(PipeTest, OpenViaProcSelfFD) { - SKIP_IF(!CreateBlocking()); - SKIP_IF(IsNamedPipe()); - - // Close the write end of the pipe. - ASSERT_THAT(close(wfd_.release()), SyscallSucceeds()); - - // Open other side via /proc/self/fd. It should not block. - FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(absl::StrCat("/proc/self/fd/", rfd_.get()), O_RDONLY)); -} - -// Test that opening and reading from an anonymous pipe (with existing writes) -// RDONLY via /proc/self/fd/N returns the existing data. -TEST_P(PipeTest, OpenViaProcSelfFDWithWrites) { - SKIP_IF(!CreateBlocking()); - SKIP_IF(IsNamedPipe()); - - // Write to the pipe and then close the write fd. - int wbuf = kTestValue; - ASSERT_THAT(write(wfd_.get(), &wbuf, sizeof(wbuf)), - SyscallSucceedsWithValue(sizeof(wbuf))); - ASSERT_THAT(close(wfd_.release()), SyscallSucceeds()); - - // Open read side via /proc/self/fd, and read from it. - FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(absl::StrCat("/proc/self/fd/", rfd_.get()), O_RDONLY)); - int rbuf; - ASSERT_THAT(read(proc_self_fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(wbuf, rbuf); -} - -// Test that accesses of /proc/<PID>/fd correctly decrement the refcount. -TEST_P(PipeTest, ProcFDReleasesFile) { - SKIP_IF(!CreateBlocking()); - - // Stat the pipe FD, which shouldn't alter the refcount. - struct stat wst; - ASSERT_THAT(lstat(absl::StrCat("/proc/self/fd/", wfd_.get()).c_str(), &wst), - SyscallSucceeds()); - - // Close the write end and ensure that read indicates EOF. - wfd_.reset(); - char buf; - ASSERT_THAT(read(rfd_.get(), &buf, 1), SyscallSucceedsWithValue(0)); -} - -// Same for /proc/<PID>/fdinfo. -TEST_P(PipeTest, ProcFDInfoReleasesFile) { - SKIP_IF(!CreateBlocking()); - - // Stat the pipe FD, which shouldn't alter the refcount. - struct stat wst; - ASSERT_THAT( - lstat(absl::StrCat("/proc/self/fdinfo/", wfd_.get()).c_str(), &wst), - SyscallSucceeds()); - - // Close the write end and ensure that read indicates EOF. - wfd_.reset(); - char buf; - ASSERT_THAT(read(rfd_.get(), &buf, 1), SyscallSucceedsWithValue(0)); -} - -TEST_P(PipeTest, SizeChange) { - SKIP_IF(!CreateBlocking()); - - // Set the minimum possible size. - ASSERT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, 0), SyscallSucceeds()); - int min = Size(); - EXPECT_GT(min, 0); // Should be rounded up. - - // Set from the read end. - ASSERT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, min + 1), SyscallSucceeds()); - int med = Size(); - EXPECT_GT(med, min); // Should have grown, may be rounded. - - // Set from the write end. - ASSERT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, med + 1), SyscallSucceeds()); - int max = Size(); - EXPECT_GT(max, med); // Ditto. -} - -TEST_P(PipeTest, SizeChangeMax) { - SKIP_IF(!CreateBlocking()); - - // Assert there's some maximum. - EXPECT_THAT(fcntl(rfd_.get(), F_SETPIPE_SZ, 0x7fffffffffffffff), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, 0x7fffffffffffffff), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(PipeTest, SizeChangeFull) { - SKIP_IF(!CreateBlocking()); - - // Ensure that we adjust to a large enough size to avoid rounding when we - // perform the size decrease. If rounding occurs, we may not actually - // adjust the size and the call below will return success. It was found via - // experimentation that this granularity avoids the rounding for Linux. - constexpr int kDelta = 64 * 1024; - ASSERT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, Size() + kDelta), - SyscallSucceeds()); - - // Fill the buffer and try to change down. - std::vector<char> buf(Size()); - ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - EXPECT_THAT(fcntl(wfd_.get(), F_SETPIPE_SZ, Size() - kDelta), - SyscallFailsWithErrno(EBUSY)); -} - -TEST_P(PipeTest, Streaming) { - SKIP_IF(!CreateBlocking()); - - // We make too many calls to go through full save cycles. - DisableSave ds; - - // Size() requires 2 syscalls, call it once and remember the value. - const size_t pipe_size = Size(); - const size_t streamed_bytes = 4 * pipe_size; - - absl::Notification notify; - ScopedThread t([&, this]() { - std::vector<char> buf(1024); - // Don't start until it's full. - notify.WaitForNotification(); - size_t total = 0; - while (total < streamed_bytes) { - ASSERT_THAT(read(rfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - total += buf.size(); - } - }); - - // Write 4 bytes * pipe_size. It will fill up the pipe once, notify the reader - // to start. Then we write pipe size worth 3 more times to ensure the reader - // can follow along. - // - // The size of each write (which is determined by buf.size()) must be smaller - // than the size of the pipe (which, in the "smallbuffer" configuration, is 1 - // page) for the check for notify.Notify() below to be correct. - std::vector<char> buf(1024); - RandomizeBuffer(buf.data(), buf.size()); - size_t total = 0; - while (total < streamed_bytes) { - ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - total += buf.size(); - - // Is the next write about to fill up the buffer? Wake up the reader once. - if (total < pipe_size && (total + buf.size()) >= pipe_size) { - notify.Notify(); - } - } -} - -std::string PipeCreatorName(::testing::TestParamInfo<PipeCreator> info) { - return info.param.name_; // Use the name specified. -} - -INSTANTIATE_TEST_SUITE_P( - Pipes, PipeTest, - ::testing::Values( - PipeCreator{ - "pipe", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - *is_blocking = true; - *is_namedpipe = false; - }, - }, - PipeCreator{ - "pipe2blocking", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - ASSERT_THAT(pipe2(fds, 0), SyscallSucceeds()); - *is_blocking = true; - *is_namedpipe = false; - }, - }, - PipeCreator{ - "pipe2nonblocking", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds()); - *is_blocking = false; - *is_namedpipe = false; - }, - }, - PipeCreator{ - "smallbuffer", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - // Set to the minimum available size (will round up). - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - ASSERT_THAT(fcntl(fds[0], F_SETPIPE_SZ, 0), SyscallSucceeds()); - *is_blocking = true; - *is_namedpipe = false; - }, - }, - PipeCreator{ - "namednonblocking", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - // Create a new file-based pipe (non-blocking). - std::string path; - { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - path = file.path(); - } - SKIP_IF(mkfifo(path.c_str(), 0644) != 0); - fds[0] = open(path.c_str(), O_NONBLOCK | O_RDONLY); - fds[1] = open(path.c_str(), O_NONBLOCK | O_WRONLY); - MaybeSave(); - *is_blocking = false; - *is_namedpipe = true; - }, - }, - PipeCreator{ - "namedblocking", - [](int fds[2], bool* is_blocking, bool* is_namedpipe) { - // Create a new file-based pipe (blocking). - std::string path; - { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - path = file.path(); - } - SKIP_IF(mkfifo(path.c_str(), 0644) != 0); - ScopedThread t( - [&path, &fds]() { fds[1] = open(path.c_str(), O_WRONLY); }); - fds[0] = open(path.c_str(), O_RDONLY); - t.Join(); - MaybeSave(); - *is_blocking = true; - *is_namedpipe = true; - }, - }), - PipeCreatorName); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/poll.cc b/test/syscalls/linux/poll.cc deleted file mode 100644 index 7a316427d..000000000 --- a/test/syscalls/linux/poll.cc +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <poll.h> -#include <sys/resource.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include <algorithm> -#include <iostream> - -#include "gtest/gtest.h" -#include "absl/synchronization/notification.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/base_poll_test.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -class PollTest : public BasePollTest { - protected: - void SetUp() override { BasePollTest::SetUp(); } - void TearDown() override { BasePollTest::TearDown(); } -}; - -TEST_F(PollTest, InvalidFds) { - // fds is invalid because it's null, but we tell ppoll the length is non-zero. - EXPECT_THAT(poll(nullptr, 1, 1), SyscallFailsWithErrno(EFAULT)); - EXPECT_THAT(poll(nullptr, -1, 1), SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(PollTest, NullFds) { - EXPECT_THAT(poll(nullptr, 0, 10), SyscallSucceeds()); -} - -TEST_F(PollTest, ZeroTimeout) { - EXPECT_THAT(poll(nullptr, 0, 0), SyscallSucceeds()); -} - -// If random S/R interrupts the poll, SIGALRM may be delivered before poll -// restarts, causing the poll to hang forever. -TEST_F(PollTest, NegativeTimeout_NoRandomSave) { - // Negative timeout mean wait forever so set a timer. - SetTimer(absl::Milliseconds(100)); - EXPECT_THAT(poll(nullptr, 0, -1), SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); -} - -TEST_F(PollTest, NonBlockingEventPOLLIN) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Write some data to the pipe. - char s[] = "foo\n"; - ASSERT_THAT(WriteFd(fd1.get(), s, strlen(s) + 1), SyscallSucceeds()); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {fd0.get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1)); - - // Should trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN); -} - -TEST_F(PollTest, BlockingEventPOLLIN) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Start a blocking poll on the read fd. - absl::Notification notify; - ScopedThread t([&fd0, ¬ify]() { - notify.Notify(); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {fd0.get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, -1), SyscallSucceedsWithValue(1)); - - // Should trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN); - }); - - notify.WaitForNotification(); - absl::SleepFor(absl::Seconds(1.0)); - - // Write some data to the pipe. - char s[] = "foo\n"; - ASSERT_THAT(WriteFd(fd1.get(), s, strlen(s) + 1), SyscallSucceeds()); -} - -TEST_F(PollTest, NonBlockingEventPOLLHUP) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Close the writer fd. - fd1.reset(); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {fd0.get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1)); - - // Should trigger POLLHUP event. - EXPECT_EQ(poll_fd.revents & POLLHUP, POLLHUP); - - // Should not trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, 0); -} - -TEST_F(PollTest, BlockingEventPOLLHUP) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Start a blocking poll on the read fd. - absl::Notification notify; - ScopedThread t([&fd0, ¬ify]() { - notify.Notify(); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {fd0.get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, -1), SyscallSucceedsWithValue(1)); - - // Should trigger POLLHUP event. - EXPECT_EQ(poll_fd.revents & POLLHUP, POLLHUP); - - // Should not trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, 0); - }); - - notify.WaitForNotification(); - absl::SleepFor(absl::Seconds(1.0)); - - // Write some data and close the writer fd. - fd1.reset(); -} - -TEST_F(PollTest, NonBlockingEventPOLLERR) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Close the reader fd. - fd0.reset(); - - // Poll on the writer fd with POLLOUT event. - struct pollfd poll_fd = {fd1.get(), POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1)); - - // Should trigger POLLERR event. - EXPECT_EQ(poll_fd.revents & POLLERR, POLLERR); - - // Should also trigger POLLOUT event. - EXPECT_EQ(poll_fd.revents & POLLOUT, POLLOUT); -} - -// This test will validate that if an FD is already ready on some event, whether -// it's POLLIN or POLLOUT it will not immediately return unless that's actually -// what the caller was interested in. -TEST_F(PollTest, ImmediatelyReturnOnlyOnPollEvents) { - // Create a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Wait for read related event on the write side of the pipe, since a write - // is possible on fds[1] it would mean that POLLOUT would return immediately. - // We should make sure that we're not woken up with that state that we didn't - // specificially request. - constexpr int kTimeoutMs = 100; - struct pollfd poll_fd = {fd1.get(), POLLIN | POLLPRI | POLLRDHUP, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kTimeoutMs), - SyscallSucceedsWithValue(0)); // We should timeout. - EXPECT_EQ(poll_fd.revents, 0); // Nothing should be in returned events. - - // Now let's poll on POLLOUT and we should get back 1 fd as being ready and - // it should contain POLLOUT in the revents. - poll_fd.events = POLLOUT; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kTimeoutMs), - SyscallSucceedsWithValue(1)); // 1 fd should have an event. - EXPECT_EQ(poll_fd.revents, POLLOUT); // POLLOUT should be in revents. -} - -// This test validates that poll(2) while data is available immediately returns. -TEST_F(PollTest, PollLevelTriggered) { - int fds[2] = {}; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, /*protocol=*/0, fds), - SyscallSucceeds()); - - FileDescriptor fd0(fds[0]); - FileDescriptor fd1(fds[1]); - - // Write two bytes to the socket. - const char* kBuf = "aa"; - ASSERT_THAT(RetryEINTR(send)(fd0.get(), kBuf, /*len=*/2, /*flags=*/0), - SyscallSucceedsWithValue(2)); // 2 bytes should be written. - - // Poll(2) should immediately return as there is data available to read. - constexpr int kInfiniteTimeout = -1; - struct pollfd poll_fd = {fd1.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, /*nfds=*/1, kInfiniteTimeout), - SyscallSucceedsWithValue(1)); // 1 fd should be ready to read. - EXPECT_NE(poll_fd.revents & POLLIN, 0); - - // Read a single byte. - char read_byte = 0; - ASSERT_THAT(RetryEINTR(recv)(fd1.get(), &read_byte, /*len=*/1, /*flags=*/0), - SyscallSucceedsWithValue(1)); // 1 byte should be read. - ASSERT_EQ(read_byte, 'a'); // We should have read a single 'a'. - - // Create a separate pollfd for our second poll. - struct pollfd poll_fd_after = {fd1.get(), POLLIN, 0}; - - // Poll(2) should again immediately return since we only read one byte. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd_after, /*nfds=*/1, kInfiniteTimeout), - SyscallSucceedsWithValue(1)); // 1 fd should be ready to read. - EXPECT_NE(poll_fd_after.revents & POLLIN, 0); -} - -TEST_F(PollTest, Nfds) { - // Stash value of RLIMIT_NOFILES. - struct rlimit rlim; - TEST_PCHECK(getrlimit(RLIMIT_NOFILE, &rlim) == 0); - - // gVisor caps the number of FDs that epoll can use beyond RLIMIT_NOFILE. - constexpr rlim_t maxFD = 4096; - if (rlim.rlim_cur > maxFD) { - rlim.rlim_cur = maxFD; - TEST_PCHECK(setrlimit(RLIMIT_NOFILE, &rlim) == 0); - } - - rlim_t max_fds = rlim.rlim_cur; - std::cout << "Using limit: " << max_fds << std::endl; - - // Create an eventfd. Since its value is initially zero, it is writable. - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - - // Create the biggest possible pollfd array such that each element is valid. - // Each entry in the 'fds' array refers to the eventfd and polls for - // "writable" events (events=POLLOUT). This essentially guarantees that the - // poll() is a no-op and allows negative testing of the 'nfds' parameter. - std::vector<struct pollfd> fds(max_fds + 1, - {.fd = efd.get(), .events = POLLOUT}); - - // Verify that 'nfds' up to RLIMIT_NOFILE are allowed. - EXPECT_THAT(RetryEINTR(poll)(fds.data(), 1, 1), SyscallSucceedsWithValue(1)); - EXPECT_THAT(RetryEINTR(poll)(fds.data(), max_fds / 2, 1), - SyscallSucceedsWithValue(max_fds / 2)); - EXPECT_THAT(RetryEINTR(poll)(fds.data(), max_fds, 1), - SyscallSucceedsWithValue(max_fds)); - - // If 'nfds' exceeds RLIMIT_NOFILE then it must fail with EINVAL. - EXPECT_THAT(poll(fds.data(), max_fds + 1, 1), SyscallFailsWithErrno(EINVAL)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ppoll.cc b/test/syscalls/linux/ppoll.cc deleted file mode 100644 index 8245a11e8..000000000 --- a/test/syscalls/linux/ppoll.cc +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <poll.h> -#include <signal.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/base_poll_test.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -// Linux and glibc have a different idea of the sizeof sigset_t. When calling -// the syscall directly, use what the kernel expects. -unsigned kSigsetSize = SIGRTMAX / 8; - -// Linux ppoll(2) differs from the glibc wrapper function in that Linux updates -// the timeout with the amount of time remaining. In order to test this behavior -// we need to use the syscall directly. -int syscallPpoll(struct pollfd* fds, nfds_t nfds, struct timespec* timeout_ts, - const sigset_t* sigmask, unsigned mask_size) { - return syscall(SYS_ppoll, fds, nfds, timeout_ts, sigmask, mask_size); -} - -class PpollTest : public BasePollTest { - protected: - void SetUp() override { BasePollTest::SetUp(); } - void TearDown() override { BasePollTest::TearDown(); } -}; - -TEST_F(PpollTest, InvalidFds) { - // fds is invalid because it's null, but we tell ppoll the length is non-zero. - struct timespec timeout = {}; - sigset_t sigmask; - TEST_PCHECK(sigemptyset(&sigmask) == 0); - EXPECT_THAT(syscallPpoll(nullptr, 1, &timeout, &sigmask, kSigsetSize), - SyscallFailsWithErrno(EFAULT)); - EXPECT_THAT(syscallPpoll(nullptr, -1, &timeout, &sigmask, kSigsetSize), - SyscallFailsWithErrno(EINVAL)); -} - -// See that when fds is null, ppoll behaves like sleep. -TEST_F(PpollTest, NullFds) { - struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10)); - ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 0); -} - -TEST_F(PpollTest, ZeroTimeout) { - struct timespec timeout = {}; - ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 0); -} - -// If random S/R interrupts the ppoll, SIGALRM may be delivered before ppoll -// restarts, causing the ppoll to hang forever. -TEST_F(PpollTest, NoTimeout_NoRandomSave) { - // When there's no timeout, ppoll may never return so set a timer. - SetTimer(absl::Milliseconds(100)); - // See that we get interrupted by the timer. - ASSERT_THAT(syscallPpoll(nullptr, 0, nullptr, nullptr, 0), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); -} - -TEST_F(PpollTest, InvalidTimeoutNegative) { - struct timespec timeout = absl::ToTimespec(absl::Nanoseconds(-1)); - EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(PpollTest, InvalidTimeoutNotNormalized) { - struct timespec timeout = {0, 1000000001}; - EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, nullptr, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(PpollTest, InvalidMaskSize) { - struct timespec timeout = {}; - sigset_t sigmask; - TEST_PCHECK(sigemptyset(&sigmask) == 0); - EXPECT_THAT(syscallPpoll(nullptr, 0, &timeout, &sigmask, 128), - SyscallFailsWithErrno(EINVAL)); -} - -// Verify that signals blocked by the ppoll mask (that would otherwise be -// allowed) do not interrupt ppoll. -TEST_F(PpollTest, SignalMaskBlocksSignal) { - absl::Duration duration(absl::Seconds(30)); - struct timespec timeout = absl::ToTimespec(duration); - absl::Duration timer_duration(absl::Seconds(10)); - - // Call with a mask that blocks SIGALRM. See that ppoll is not interrupted - // (i.e. returns 0) and that upon completion, the timer has fired. - sigset_t mask; - ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds()); - TEST_PCHECK(sigaddset(&mask, SIGALRM) == 0); - SetTimer(timer_duration); - MaybeSave(); - ASSERT_FALSE(TimerFired()); - ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, &mask, kSigsetSize), - SyscallSucceeds()); - EXPECT_TRUE(TimerFired()); - EXPECT_EQ(absl::DurationFromTimespec(timeout), absl::Duration()); -} - -// Verify that signals allowed by the ppoll mask (that would otherwise be -// blocked) interrupt ppoll. -TEST_F(PpollTest, SignalMaskAllowsSignal) { - absl::Duration duration(absl::Seconds(30)); - struct timespec timeout = absl::ToTimespec(duration); - absl::Duration timer_duration(absl::Seconds(10)); - - sigset_t mask; - ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds()); - - // Block SIGALRM. - auto cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, SIGALRM)); - - // Call with a mask that unblocks SIGALRM. See that ppoll is interrupted. - SetTimer(timer_duration); - MaybeSave(); - ASSERT_FALSE(TimerFired()); - ASSERT_THAT(syscallPpoll(nullptr, 0, &timeout, &mask, kSigsetSize), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); - EXPECT_GT(absl::DurationFromTimespec(timeout), absl::Duration()); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc deleted file mode 100644 index f675dc430..000000000 --- a/test/syscalls/linux/prctl.cc +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/prctl.h> -#include <sys/ptrace.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(bool, prctl_no_new_privs_test_child, false, - "If true, exit with the return value of prctl(PR_GET_NO_NEW_PRIVS) " - "plus an offset (see test source)."); - -namespace gvisor { -namespace testing { - -namespace { - -#ifndef SUID_DUMP_DISABLE -#define SUID_DUMP_DISABLE 0 -#endif /* SUID_DUMP_DISABLE */ -#ifndef SUID_DUMP_USER -#define SUID_DUMP_USER 1 -#endif /* SUID_DUMP_USER */ -#ifndef SUID_DUMP_ROOT -#define SUID_DUMP_ROOT 2 -#endif /* SUID_DUMP_ROOT */ - -TEST(PrctlTest, NameInitialized) { - const size_t name_length = 20; - char name[name_length] = {}; - ASSERT_THAT(prctl(PR_GET_NAME, name), SyscallSucceeds()); - ASSERT_NE(std::string(name), ""); -} - -TEST(PrctlTest, SetNameLongName) { - const size_t name_length = 20; - const std::string long_name(name_length, 'A'); - ASSERT_THAT(prctl(PR_SET_NAME, long_name.c_str()), SyscallSucceeds()); - char truncated_name[name_length] = {}; - ASSERT_THAT(prctl(PR_GET_NAME, truncated_name), SyscallSucceeds()); - const size_t truncated_length = 15; - ASSERT_EQ(long_name.substr(0, truncated_length), std::string(truncated_name)); -} - -TEST(PrctlTest, ChildProcessName) { - constexpr size_t kMaxNameLength = 15; - - char parent_name[kMaxNameLength + 1] = {}; - memset(parent_name, 'a', kMaxNameLength); - - ASSERT_THAT(prctl(PR_SET_NAME, parent_name), SyscallSucceeds()); - - pid_t child_pid = fork(); - TEST_PCHECK(child_pid >= 0); - if (child_pid == 0) { - char child_name[kMaxNameLength + 1] = {}; - TEST_PCHECK(prctl(PR_GET_NAME, child_name) >= 0); - TEST_CHECK(memcmp(parent_name, child_name, sizeof(parent_name)) == 0); - _exit(0); - } - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status =" << status; -} - -// Offset added to exit code from test child to distinguish from other abnormal -// exits. -constexpr int kPrctlNoNewPrivsTestChildExitBase = 100; - -TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) { - // Check if no_new_privs is already set. If it is, we can still test that it's - // preserved across clone/fork/execve, but we also expect it to still be set - // at the end of the test. Otherwise, call prctl(PR_SET_NO_NEW_PRIVS) so as - // not to contaminate the original thread. - int no_new_privs; - ASSERT_THAT(no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceeds()); - ScopedThread([] { - ASSERT_THAT(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), SyscallSucceeds()); - EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceedsWithValue(1)); - ScopedThread([] { - EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceedsWithValue(1)); - // Note that these ASSERT_*s failing will only return from this thread, - // but this is the intended behavior. - pid_t child_pid = -1; - int execve_errno = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/proc/self/exe", - {"/proc/self/exe", "--prctl_no_new_privs_test_child"}, {}, - nullptr, &child_pid, &execve_errno)); - - ASSERT_GT(child_pid, 0); - ASSERT_EQ(execve_errno, 0); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceeds()); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), kPrctlNoNewPrivsTestChildExitBase + 1); - - EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceedsWithValue(1)); - }); - EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceedsWithValue(1)); - }); - EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), - SyscallSucceedsWithValue(no_new_privs)); -} - -TEST(PrctlTest, PDeathSig) { - pid_t child_pid; - - // Make the new process' parent a separate thread since the parent death - // signal fires when the parent *thread* exits. - ScopedThread([&] { - child_pid = fork(); - TEST_CHECK(child_pid >= 0); - if (child_pid == 0) { - // In child process. - TEST_CHECK(prctl(PR_SET_PDEATHSIG, SIGKILL) >= 0); - int signo; - TEST_CHECK(prctl(PR_GET_PDEATHSIG, &signo) >= 0); - TEST_CHECK(signo == SIGKILL); - // Enable tracing, then raise SIGSTOP and expect our parent to suppress - // it. - TEST_CHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) >= 0); - TEST_CHECK(raise(SIGSTOP) == 0); - // Sleep until killed by our parent death signal. sleep(3) is - // async-signal-safe, absl::SleepFor isn't. - while (true) { - sleep(10); - } - } - // In parent process. - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << "status = " << status; - - // Suppress the SIGSTOP and detach from the child. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - }); - - // The child should have been killed by its parent death SIGKILL. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << "status = " << status; -} - -// This test is to validate that calling prctl with PR_SET_MM without the -// CAP_SYS_RESOURCE returns EPERM. -TEST(PrctlTest, InvalidPrSetMM) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) { - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE, - false)); // Drop capability to test below. - } - ASSERT_THAT(prctl(PR_SET_MM, 0, 0, 0, 0), SyscallFailsWithErrno(EPERM)); -} - -// Sanity check that dumpability is remembered. -TEST(PrctlTest, SetGetDumpability) { - int before; - ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds()); - auto cleanup = Cleanup([before] { - ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds()); - }); - - EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_DISABLE), SyscallSucceeds()); - EXPECT_THAT(prctl(PR_GET_DUMPABLE), - SyscallSucceedsWithValue(SUID_DUMP_DISABLE)); - - EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_USER), SyscallSucceeds()); - EXPECT_THAT(prctl(PR_GET_DUMPABLE), SyscallSucceedsWithValue(SUID_DUMP_USER)); -} - -// SUID_DUMP_ROOT cannot be set via PR_SET_DUMPABLE. -TEST(PrctlTest, RootDumpability) { - EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_ROOT), - SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_prctl_no_new_privs_test_child)) { - exit(gvisor::testing::kPrctlNoNewPrivsTestChildExitBase + - prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0)); - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/prctl_setuid.cc b/test/syscalls/linux/prctl_setuid.cc deleted file mode 100644 index c4e9cf528..000000000 --- a/test/syscalls/linux/prctl_setuid.cc +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sched.h> -#include <sys/prctl.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "test/util/capability_util.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(int32_t, scratch_uid, 65534, "scratch UID"); -// This flag is used to verify that after an exec PR_GET_KEEPCAPS -// returns 0, the return code will be offset by kPrGetKeepCapsExitBase. -ABSL_FLAG(bool, prctl_pr_get_keepcaps, false, - "If true the test will verify that prctl with pr_get_keepcaps" - "returns 0. The test will exit with the result of that check."); - -// These tests exist seperately from prctl because we need to start -// them as root. Setuid() has the behavior that permissions are fully -// removed if one of the UIDs were 0 before a setuid() call. This -// behavior can be changed by using PR_SET_KEEPCAPS and that is what -// is tested here. -// -// Reference setuid(2): -// The setuid() function checks the effective user ID of -// the caller and if it is the superuser, all process-related user ID's -// are set to uid. After this has occurred, it is impossible for the -// program to regain root privileges. -// -// Thus, a set-user-ID-root program wishing to temporarily drop root -// privileges, assume the identity of an unprivileged user, and then -// regain root privileges afterward cannot use setuid(). You can -// accomplish this with seteuid(2). -namespace gvisor { -namespace testing { - -// Offset added to exit code from test child to distinguish from other abnormal -// exits. -constexpr int kPrGetKeepCapsExitBase = 100; - -namespace { - -class PrctlKeepCapsSetuidTest : public ::testing::Test { - protected: - void SetUp() override { - // PR_GET_KEEPCAPS will only return 0 or 1 (on success). - ASSERT_THAT(original_keepcaps_ = prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), - SyscallSucceeds()); - ASSERT_TRUE(original_keepcaps_ == 0 || original_keepcaps_ == 1); - } - - void TearDown() override { - // Restore PR_SET_KEEPCAPS. - ASSERT_THAT(prctl(PR_SET_KEEPCAPS, original_keepcaps_, 0, 0, 0), - SyscallSucceeds()); - - // Verify that it was restored. - ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), - SyscallSucceedsWithValue(original_keepcaps_)); - } - - // The original keep caps value exposed so tests can use it if they need. - int original_keepcaps_ = 0; -}; - -// This test will verify that a bad value, eg. not 0 or 1 for -// PR_SET_KEEPCAPS will return EINVAL as required by prctl(2). -TEST_F(PrctlKeepCapsSetuidTest, PrctlBadArgsToKeepCaps) { - ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 2, 0, 0, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// This test will verify that a setuid(2) without PR_SET_KEEPCAPS will cause -// all capabilities to be dropped. -TEST_F(PrctlKeepCapsSetuidTest, SetUidNoKeepCaps) { - // getuid(2) never fails. - if (getuid() != 0) { - SKIP_IF(!IsRunningOnGvisor()); - FAIL() << "User is not root on gvisor platform."; - } - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting - // this test. Otherwise, the files are created by root (UID before the - // test), but cannot be opened by the `uid` set below after the test. After - // calling setuid(non-zero-UID), there is no way to get root privileges - // back. - ScopedThread([] { - // Start by verifying we have a capability. - TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); - - // Verify that PR_GET_KEEPCAPS is disabled. - ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), - SyscallSucceedsWithValue(0)); - - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. POSIX threads, however, require that - // all threads have the same UIDs, so using the setuid wrapper sets all - // threads' real UID. - EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)), - SyscallSucceeds()); - - // Verify that we changed uid. - EXPECT_THAT(getuid(), - SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid))); - - // Verify we lost the capability in the effective set, this always happens. - TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); - - // We should have also lost it in the permitted set by the setuid() so - // SetCapability should fail when we try to add it back to the effective set - ASSERT_FALSE(SetCapability(CAP_SYS_ADMIN, true).ok()); - }); -} - -// This test will verify that a setuid with PR_SET_KEEPCAPS will cause -// capabilities to be retained after we switch away from the root user. -TEST_F(PrctlKeepCapsSetuidTest, SetUidKeepCaps) { - // getuid(2) never fails. - if (getuid() != 0) { - SKIP_IF(!IsRunningOnGvisor()); - FAIL() << "User is not root on gvisor platform."; - } - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting - // this test. Otherwise, the files are created by root (UID before the - // test), but cannot be opened by the `uid` set below after the test. After - // calling setuid(non-zero-UID), there is no way to get root privileges - // back. - ScopedThread([] { - // Start by verifying we have a capability. - TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); - - // Set PR_SET_KEEPCAPS. - ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); - - // Verify PR_SET_KEEPCAPS was set before we proceed. - ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), - SyscallSucceedsWithValue(1)); - - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. POSIX threads, however, require that - // all threads have the same UIDs, so using the setuid wrapper sets all - // threads' real UID. - EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)), - SyscallSucceeds()); - - // Verify that we changed uid. - EXPECT_THAT(getuid(), - SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid))); - - // Verify we lost the capability in the effective set, this always happens. - TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); - - // We lost the capability in the effective set, but it will still - // exist in the permitted set so we can elevate the capability. - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, true)); - - // Verify we got back the capability in the effective set. - TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); - }); -} - -// This test will verify that PR_SET_KEEPCAPS is not retained -// across an execve. According to prctl(2): -// "The "keep capabilities" value will be reset to 0 on subsequent -// calls to execve(2)." -TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterExec) { - ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); - - // Verify PR_SET_KEEPCAPS was set before we proceed. - ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(1)); - - pid_t child_pid = -1; - int execve_errno = 0; - // Do an exec and then verify that PR_GET_KEEPCAPS returns 0 - // see the body of main below. - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - "/proc/self/exe", {"/proc/self/exe", "--prctl_pr_get_keepcaps"}, {}, - nullptr, &child_pid, &execve_errno)); - - ASSERT_GT(child_pid, 0); - ASSERT_EQ(execve_errno, 0); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - ASSERT_TRUE(WIFEXITED(status)); - // PR_SET_KEEPCAPS should have been cleared by the exec. - // Success should return gvisor::testing::kPrGetKeepCapsExitBase + 0 - ASSERT_EQ(WEXITSTATUS(status), kPrGetKeepCapsExitBase); -} - -TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterNewUserNamespace) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - - // Fork to avoid changing the user namespace of the original test process. - pid_t const child_pid = fork(); - - if (child_pid == 0) { - // Verify that the keepcaps flag is set to 0 when we change user namespaces. - TEST_PCHECK(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0); - MaybeSave(); - - TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 1); - MaybeSave(); - - TEST_PCHECK(unshare(CLONE_NEWUSER) == 0); - MaybeSave(); - - TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 0); - MaybeSave(); - - _exit(0); - } - - int status; - ASSERT_THAT(child_pid, SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status = " << status; -} - -// This test will verify that PR_SET_KEEPCAPS and PR_GET_KEEPCAPS work correctly -TEST_F(PrctlKeepCapsSetuidTest, PrGetKeepCaps) { - // Set PR_SET_KEEPCAPS to the negation of the original. - ASSERT_THAT(prctl(PR_SET_KEEPCAPS, !original_keepcaps_, 0, 0, 0), - SyscallSucceeds()); - - // Verify it was set. - ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), - SyscallSucceedsWithValue(!original_keepcaps_)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_prctl_pr_get_keepcaps)) { - return gvisor::testing::kPrGetKeepCapsExitBase + - prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0); - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/pread64.cc b/test/syscalls/linux/pread64.cc deleted file mode 100644 index c74990ba1..000000000 --- a/test/syscalls/linux/pread64.cc +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/unistd.h> -#include <sys/mman.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class Pread64Test : public ::testing::Test { - void SetUp() override { - name_ = NewTempAbsPath(); - ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_CREAT, 0644)); - } - - void TearDown() override { unlink(name_.c_str()); } - - public: - std::string name_; -}; - -TEST(Pread64TestNoTempFile, BadFileDescriptor) { - char buf[1024]; - EXPECT_THAT(pread64(-1, buf, 1024, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(Pread64Test, ZeroBuffer) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR)); - - char msg[] = "hello world"; - EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0), - SyscallSucceedsWithValue(strlen(msg))); - - char buf[10]; - EXPECT_THAT(pread64(fd.get(), buf, 0, 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(Pread64Test, BadBuffer) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR)); - - char msg[] = "hello world"; - EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0), - SyscallSucceedsWithValue(strlen(msg))); - - char* bad_buffer = nullptr; - EXPECT_THAT(pread64(fd.get(), bad_buffer, 1024, 0), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(Pread64Test, WriteOnlyNotReadable) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_WRONLY)); - - char buf[1024]; - EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(Pread64Test, Pread64WithOpath) { - 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_PATH)); - - char buf[1024]; - EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(Pread64Test, DirNotReadable) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); - - char buf[1024]; - EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(Pread64Test, BadOffset) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDONLY)); - - char buf[1024]; - EXPECT_THAT(pread64(fd.get(), buf, 1024, -1), SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(Pread64Test, OffsetNotIncremented) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDWR)); - - char msg[] = "hello world"; - EXPECT_THAT(write(fd.get(), msg, strlen(msg)), - SyscallSucceedsWithValue(strlen(msg))); - int offset; - EXPECT_THAT(offset = lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds()); - - char buf1[1024]; - EXPECT_THAT(pread64(fd.get(), buf1, 1024, 0), - SyscallSucceedsWithValue(strlen(msg))); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(offset)); - - char buf2[1024]; - EXPECT_THAT(pread64(fd.get(), buf2, 1024, 3), - SyscallSucceedsWithValue(strlen(msg) - 3)); - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(offset)); -} - -TEST_F(Pread64Test, EndOfFile) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(name_, O_RDONLY)); - - char buf[1024]; - EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallSucceedsWithValue(0)); -} - -int memfd_create(const std::string& name, unsigned int flags) { - return syscall(__NR_memfd_create, name.c_str(), flags); -} - -TEST_F(Pread64Test, Overflow) { - int f = memfd_create("negative", 0); - const FileDescriptor fd(f); - - EXPECT_THAT(ftruncate(fd.get(), 0x7fffffffffffffffull), SyscallSucceeds()); - - char buf[10]; - EXPECT_THAT(pread64(fd.get(), buf, sizeof(buf), 0x7fffffffffffffffull), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(Pread64TestNoTempFile, CantReadSocketPair_NoRandomSave) { - int sock_fds[2]; - EXPECT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds), SyscallSucceeds()); - - char buf[1024]; - EXPECT_THAT(pread64(sock_fds[0], buf, 1024, 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(pread64(sock_fds[1], buf, 1024, 0), - SyscallFailsWithErrno(ESPIPE)); - - EXPECT_THAT(close(sock_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(sock_fds[1]), SyscallSucceeds()); -} - -TEST(Pread64TestNoTempFile, CantReadPipe) { - char buf[1024]; - - int pipe_fds[2]; - EXPECT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - EXPECT_THAT(pread64(pipe_fds[0], buf, 1024, 0), - SyscallFailsWithErrno(ESPIPE)); - - EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/preadv.cc b/test/syscalls/linux/preadv.cc deleted file mode 100644 index 1c40f0915..000000000 --- a/test/syscalls/linux/preadv.cc +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <atomic> -#include <string> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Stress copy-on-write. Attempts to reproduce b/38430174. -TEST(PreadvTest, MMConcurrencyStress) { - // Fill a one-page file with zeroes (the contents don't really matter). - const auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - /* parent = */ GetAbsoluteTestTmpdir(), - /* content = */ std::string(kPageSize, 0), TempPath::kDefaultFileMode)); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Get a one-page private mapping to read to. - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - - // Repeatedly fork in a separate thread to force the mapping to become - // copy-on-write. - std::atomic<bool> done(false); - const ScopedThread t([&] { - while (!done.load()) { - const pid_t pid = fork(); - TEST_CHECK(pid >= 0); - if (pid == 0) { - // In child. The parent was obviously multithreaded, so it's neither - // safe nor necessary to do much more than exit. - syscall(SYS_exit_group, 0); - } - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status = " << status; - } - }); - - // Repeatedly read to the mapping. - struct iovec iov[2]; - iov[0].iov_base = m.ptr(); - iov[0].iov_len = kPageSize / 2; - iov[1].iov_base = reinterpret_cast<void*>(m.addr() + kPageSize / 2); - iov[1].iov_len = kPageSize / 2; - constexpr absl::Duration kTestDuration = absl::Seconds(5); - const absl::Time end = absl::Now() + kTestDuration; - while (absl::Now() < end) { - // Among other causes, save/restore cycles may cause interruptions resulting - // in partial reads, so we don't expect any particular return value. - EXPECT_THAT(RetryEINTR(preadv)(fd.get(), iov, 2, 0), SyscallSucceeds()); - } - - // Stop the other thread. - done.store(true); - - // The test passes if it neither deadlocks nor crashes the OS. -} - -// This test calls preadv with an O_PATH fd. -TEST(PreadvTest, PreadvWithOpath) { - 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_PATH)); - - struct iovec iov; - iov.iov_base = nullptr; - iov.iov_len = 0; - - EXPECT_THAT(preadv(fd.get(), &iov, 1, 0), SyscallFailsWithErrno(EBADF)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/preadv2.cc b/test/syscalls/linux/preadv2.cc deleted file mode 100644 index cb58719c4..000000000 --- a/test/syscalls/linux/preadv2.cc +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/uio.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#ifndef SYS_preadv2 -#if defined(__x86_64__) -#define SYS_preadv2 327 -#elif defined(__aarch64__) -#define SYS_preadv2 286 -#else -#error "Unknown architecture" -#endif -#endif // SYS_preadv2 - -#ifndef RWF_HIPRI -#define RWF_HIPRI 0x1 -#endif // RWF_HIPRI - -constexpr int kBufSize = 1024; - -std::string SetContent() { - std::string content; - for (int i = 0; i < kBufSize; i++) { - content += static_cast<char>((i % 10) + '0'); - } - return content; -} - -ssize_t preadv2(unsigned long fd, const struct iovec* iov, unsigned long iovcnt, - off_t offset, unsigned long flags) { - // syscall on preadv2 does some weird things (see man syscall and search - // preadv2), so we insert a 0 to word align the flags argument on native. - return syscall(SYS_preadv2, fd, iov, iovcnt, offset, 0, flags); -} - -// This test is the base case where we call preadv (no offset, no flags). -TEST(Preadv2Test, TestBaseCall) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - std::string content = SetContent(); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), content, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - std::vector<char> buf(kBufSize); - struct iovec iov[2]; - iov[0].iov_base = buf.data(); - iov[0].iov_len = buf.size() / 2; - iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2); - iov[1].iov_len = content.size() / 2; - - EXPECT_THAT(preadv2(fd.get(), iov, /*iovcnt*/ 2, /*offset=*/0, /*flags=*/0), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_EQ(content, std::string(buf.data(), buf.size())); -} - -// This test is where we call preadv with an offset and no flags. -TEST(Preadv2Test, TestValidPositiveOffset) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - std::string content = SetContent(); - const std::string prefix = "0"; - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), prefix + content, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - std::vector<char> buf(kBufSize, '0'); - struct iovec iov; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/prefix.size(), - /*flags=*/0), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - EXPECT_EQ(content, std::string(buf.data(), buf.size())); -} - -// This test is the base case where we call readv by using -1 as the offset. The -// read should use the file offset, so the test increments it by one prior to -// calling preadv2. -TEST(Preadv2Test, TestNegativeOneOffset) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - std::string content = SetContent(); - const std::string prefix = "231"; - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), prefix + content, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET), - SyscallSucceedsWithValue(prefix.size())); - - std::vector<char> buf(kBufSize, '0'); - struct iovec iov; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/-1, /*flags=*/0), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(prefix.size() + buf.size())); - - EXPECT_EQ(content, std::string(buf.data(), buf.size())); -} - -// preadv2 requires if the RWF_HIPRI flag is passed, the fd must be opened with -// O_DIRECT. This test implements a correct call with the RWF_HIPRI flag. -TEST(Preadv2Test, TestCallWithRWF_HIPRI) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - std::string content = SetContent(); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), content, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - EXPECT_THAT(fsync(fd.get()), SyscallSucceeds()); - - std::vector<char> buf(kBufSize, '0'); - struct iovec iov; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - EXPECT_THAT( - preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/RWF_HIPRI), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - EXPECT_EQ(content, std::string(buf.data(), buf.size())); -} -// This test calls preadv2 with an invalid flag. -TEST(Preadv2Test, TestInvalidFlag) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY | O_DIRECT)); - - std::vector<char> buf(kBufSize, '0'); - struct iovec iov; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/0, /*flags=*/0xF0), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -// This test calls preadv2 with an invalid offset. -TEST(Preadv2Test, TestInvalidOffset) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY | O_DIRECT)); - - auto iov = absl::make_unique<struct iovec[]>(1); - iov[0].iov_base = nullptr; - iov[0].iov_len = 0; - - EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/-8, - /*flags=*/0), - SyscallFailsWithErrno(EINVAL)); -} - -// This test calls preadv with a file set O_WRONLY. -TEST(Preadv2Test, TestUnreadableFile) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - - auto iov = absl::make_unique<struct iovec[]>(1); - iov[0].iov_base = nullptr; - iov[0].iov_len = 0; - - EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, - /*offset=*/0, /*flags=*/0), - SyscallFailsWithErrno(EBADF)); -} - -// This test calls preadv2 with a file opened with O_PATH. -TEST(Preadv2Test, Preadv2WithOpath) { - SKIP_IF(IsRunningWithVFS1()); - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - auto iov = absl::make_unique<struct iovec[]>(1); - iov[0].iov_base = nullptr; - iov[0].iov_len = 0; - - EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/0, - /*flags=*/0), - SyscallFailsWithErrno(EBADF)); -} - -// Calling preadv2 with a non-negative offset calls preadv. Calling preadv with -// an unseekable file is not allowed. A pipe is used for an unseekable file. -TEST(Preadv2Test, TestUnseekableFileInvalid) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - int pipe_fds[2]; - - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - auto iov = absl::make_unique<struct iovec[]>(1); - iov[0].iov_base = nullptr; - iov[0].iov_len = 0; - - EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1, - /*offset=*/2, /*flags=*/0), - SyscallFailsWithErrno(ESPIPE)); - - EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -TEST(Preadv2Test, TestUnseekableFileValid) { - SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - int pipe_fds[2]; - - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - std::vector<char> content(32, 'X'); - - EXPECT_THAT(write(pipe_fds[1], content.data(), content.size()), - SyscallSucceedsWithValue(content.size())); - - std::vector<char> buf(content.size()); - auto iov = absl::make_unique<struct iovec[]>(1); - iov[0].iov_base = buf.data(); - iov[0].iov_len = buf.size(); - - EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1, - /*offset=*/static_cast<off_t>(-1), /*flags=*/0), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_EQ(content, buf); - - EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/priority.cc b/test/syscalls/linux/priority.cc deleted file mode 100644 index 1d9bdfa70..000000000 --- a/test/syscalls/linux/priority.cc +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/resource.h> -#include <sys/time.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_split.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// These tests are for both the getpriority(2) and setpriority(2) syscalls -// These tests are very rudimentary because getpriority and setpriority -// have not yet been fully implemented. - -// Getpriority does something -TEST(GetpriorityTest, Implemented) { - // "getpriority() can legitimately return the value -1, it is necessary to - // clear the external variable errno prior to the call" - errno = 0; - EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/0), SyscallSucceeds()); -} - -// Invalid which -TEST(GetpriorityTest, InvalidWhich) { - errno = 0; - EXPECT_THAT(getpriority(/*which=*/3, /*who=*/0), - SyscallFailsWithErrno(EINVAL)); -} - -// Process is found when which=PRIO_PROCESS -TEST(GetpriorityTest, ValidWho) { - errno = 0; - EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), SyscallSucceeds()); -} - -// Process is not found when which=PRIO_PROCESS -TEST(GetpriorityTest, InvalidWho) { - errno = 0; - // Flaky, but it's tough to avoid a race condition when finding an unused pid - EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/INT_MAX - 1), - SyscallFailsWithErrno(ESRCH)); -} - -// Setpriority does something -TEST(SetpriorityTest, Implemented) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - // No need to clear errno for setpriority(): - // "The setpriority() call returns 0 if there is no error, or -1 if there is" - EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0, /*nice=*/16), - SyscallSucceeds()); -} - -// Invalid which -TEST(Setpriority, InvalidWhich) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0, /*nice=*/16), - SyscallFailsWithErrno(EINVAL)); -} - -// Process is found when which=PRIO_PROCESS -TEST(SetpriorityTest, ValidWho) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/16), - SyscallSucceeds()); -} - -// niceval is within the range [-20, 19] -TEST(SetpriorityTest, InsideRange) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - // Set 0 < niceval < 19 - int nice = 12; - EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds()); - - errno = 0; - EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), - SyscallSucceedsWithValue(nice)); - - // Set -20 < niceval < 0 - nice = -12; - EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds()); - - errno = 0; - EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), - SyscallSucceedsWithValue(nice)); -} - -// Verify that priority/niceness are exposed via /proc/PID/stat. -TEST(SetpriorityTest, NicenessExposedViaProcfs) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - constexpr int kNiceVal = 12; - ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kNiceVal), SyscallSucceeds()); - - errno = 0; - ASSERT_THAT(getpriority(PRIO_PROCESS, getpid()), - SyscallSucceedsWithValue(kNiceVal)); - - // Now verify we can read that same value via /proc/self/stat. - std::string proc_stat; - ASSERT_NO_ERRNO(GetContents("/proc/self/stat", &proc_stat)); - std::vector<std::string> pieces = absl::StrSplit(proc_stat, ' '); - ASSERT_GT(pieces.size(), 20); - - int niceness_procfs = 0; - ASSERT_TRUE(absl::SimpleAtoi(pieces[18], &niceness_procfs)); - EXPECT_EQ(niceness_procfs, kNiceVal); -} - -// In the kernel's implementation, values outside the range of [-20, 19] are -// truncated to these minimum and maximum values. See -// https://elixir.bootlin.com/linux/v4.4/source/kernel/sys.c#L190 -TEST(SetpriorityTest, OutsideRange) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - // Set niceval > 19 - EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/100), - SyscallSucceeds()); - - errno = 0; - // Test niceval truncated to 19 - EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), - SyscallSucceedsWithValue(/*maxnice=*/19)); - - // Set niceval < -20 - EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/-100), - SyscallSucceeds()); - - errno = 0; - // Test niceval truncated to -20 - EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), - SyscallSucceedsWithValue(/*minnice=*/-20)); -} - -// Process is not found when which=PRIO_PROCESS -TEST(SetpriorityTest, InvalidWho) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - // Flaky, but it's tough to avoid a race condition when finding an unused pid - EXPECT_THAT(setpriority(PRIO_PROCESS, - /*who=*/INT_MAX - 1, - /*nice=*/16), - SyscallFailsWithErrno(ESRCH)); -} - -// Nice succeeds, correctly modifies (or in this case does not -// modify priority of process -TEST(SetpriorityTest, NiceSucceeds) { - errno = 0; - const int priority_before = getpriority(PRIO_PROCESS, /*who=*/0); - ASSERT_THAT(nice(/*inc=*/0), SyscallSucceeds()); - - // nice(0) should not change priority - EXPECT_EQ(priority_before, getpriority(PRIO_PROCESS, /*who=*/0)); -} - -// Threads resulting from clone() maintain parent's priority -// Changes to child priority do not affect parent's priority -TEST(GetpriorityTest, CloneMaintainsPriority) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE))); - - constexpr int kParentPriority = 16; - constexpr int kChildPriority = 14; - ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kParentPriority), - SyscallSucceeds()); - - ScopedThread th([]() { - // Check that priority equals that of parent thread - pid_t my_tid; - EXPECT_THAT(my_tid = syscall(__NR_gettid), SyscallSucceeds()); - EXPECT_THAT(getpriority(PRIO_PROCESS, my_tid), - SyscallSucceedsWithValue(kParentPriority)); - - // Change the child thread's priority - EXPECT_THAT(setpriority(PRIO_PROCESS, my_tid, kChildPriority), - SyscallSucceeds()); - }); - th.Join(); - - // Check that parent's priority reemained the same even though - // the child's priority was altered - EXPECT_EQ(kParentPriority, getpriority(PRIO_PROCESS, syscall(__NR_gettid))); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/priority_execve.cc b/test/syscalls/linux/priority_execve.cc deleted file mode 100644 index 5cb343bad..000000000 --- a/test/syscalls/linux/priority_execve.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <sys/types.h> -#include <unistd.h> - -int main(int argc, char** argv, char** envp) { - errno = 0; - int prio = getpriority(PRIO_PROCESS, getpid()); - - // NOTE: getpriority() can legitimately return negative values - // in the range [-20, 0). If errno is set, exit with a value that - // could not be reached by a valid priority. Valid exit values - // for the test are in the range [1, 40], so we'll use 0. - if (errno != 0) { - printf("getpriority() failed with errno = %d\n", errno); - exit(0); - } - - // Used by test to verify priority is being maintained through - // calls to execve(). Since prio should always be in the range - // [-20, 19], we offset by 20 so as not to have negative exit codes. - exit(20 - prio); - - return 0; -} diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc deleted file mode 100644 index 493042dfc..000000000 --- a/test/syscalls/linux/proc.cc +++ /dev/null @@ -1,2685 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <elf.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <linux/magic.h> -#include <linux/sem.h> -#include <sched.h> -#include <signal.h> -#include <stddef.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/prctl.h> -#include <sys/ptrace.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/utsname.h> -#include <syscall.h> -#include <unistd.h> - -#include <algorithm> -#include <atomic> -#include <functional> -#include <iostream> -#include <map> -#include <memory> -#include <ostream> -#include <regex> -#include <string> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/container/node_hash_set.h" -#include "absl/strings/ascii.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "absl/synchronization/mutex.h" -#include "absl/synchronization/notification.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/time_util.h" -#include "test/util/timer_util.h" - -// NOTE(magi): No, this isn't really a syscall but this is a really simple -// way to get it tested on both gVisor, PTrace and Linux. - -using ::testing::AllOf; -using ::testing::AnyOf; -using ::testing::ContainerEq; -using ::testing::Contains; -using ::testing::ContainsRegex; -using ::testing::Eq; -using ::testing::Gt; -using ::testing::HasSubstr; -using ::testing::IsSupersetOf; -using ::testing::Pair; -using ::testing::UnorderedElementsAre; -using ::testing::UnorderedElementsAreArray; - -// Exported by glibc. -extern char** environ; - -namespace gvisor { -namespace testing { -namespace { - -#ifndef SUID_DUMP_DISABLE -#define SUID_DUMP_DISABLE 0 -#endif /* SUID_DUMP_DISABLE */ -#ifndef SUID_DUMP_USER -#define SUID_DUMP_USER 1 -#endif /* SUID_DUMP_USER */ -#ifndef SUID_DUMP_ROOT -#define SUID_DUMP_ROOT 2 -#endif /* SUID_DUMP_ROOT */ - -#if defined(__x86_64__) || defined(__i386__) -// This list of "required" fields is taken from reading the file -// arch/x86/kernel/cpu/proc.c and seeing which fields will be unconditionally -// printed by the kernel. -static const char* required_fields[] = { - "processor", - "vendor_id", - "cpu family", - "model\t\t:", - "model name", - "stepping", - "cpu MHz", - "fpu\t\t:", - "fpu_exception", - "cpuid level", - "wp", - "bogomips", - "clflush size", - "cache_alignment", - "address sizes", - "power management", -}; -#elif __aarch64__ -// This list of "required" fields is taken from reading the file -// arch/arm64/kernel/cpuinfo.c and seeing which fields will be unconditionally -// printed by the kernel. -static const char* required_fields[] = { - "processor", "BogoMIPS", "Features", "CPU implementer", - "CPU architecture", "CPU variant", "CPU part", "CPU revision", -}; -#else -#error "Unknown architecture" -#endif - -// Takes the subprocess command line and pid. -// If it returns !OK, WithSubprocess returns immediately. -using SubprocessCallback = std::function<PosixError(int)>; - -std::vector<std::string> saved_argv; // NOLINT - -// Helper function to dump /proc/{pid}/status and check the -// state data. State should = "Z" for zombied or "RSD" for -// running, interruptible sleeping (S), or uninterruptible sleep -// (D). -void CompareProcessState(absl::string_view state, int pid) { - auto status_file = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/", pid, "/status"))); - // N.B. POSIX extended regexes don't support shorthand character classes (\w) - // inside of brackets. - EXPECT_THAT(status_file, - ContainsRegex(absl::StrCat("State:.[", state, - R"EOL(]\s+\([a-zA-Z ]+\))EOL"))); -} - -// Run callbacks while a subprocess is running, zombied, and/or exited. -PosixError WithSubprocess(SubprocessCallback const& running, - SubprocessCallback const& zombied, - SubprocessCallback const& exited) { - int pipe_fds[2] = {}; - if (pipe(pipe_fds) < 0) { - return PosixError(errno, "pipe"); - } - - int child_pid = fork(); - if (child_pid < 0) { - return PosixError(errno, "fork"); - } - - if (child_pid == 0) { - close(pipe_fds[0]); // Close the read end. - const DisableSave ds; // Timing issues. - - // Write to the pipe to tell it we're ready. - char buf = 'a'; - int res = 0; - res = WriteFd(pipe_fds[1], &buf, sizeof(buf)); - TEST_CHECK_MSG(res == sizeof(buf), "Write failure in subprocess"); - - while (true) { - SleepSafe(absl::Milliseconds(100)); - } - } - - close(pipe_fds[1]); // Close the write end. - - int status = 0; - auto wait_cleanup = Cleanup([child_pid, &status] { - EXPECT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - }); - auto kill_cleanup = Cleanup([child_pid] { - EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - }); - - // Wait for the child. - char buf = 0; - int res = ReadFd(pipe_fds[0], &buf, sizeof(buf)); - if (res < 0) { - return PosixError(errno, "Read from pipe"); - } else if (res == 0) { - return PosixError(EPIPE, "Unable to read from pipe: EOF"); - } - - if (running) { - // The first arg, RSD, refers to a "running process", or a process with a - // state of Running (R), Interruptable Sleep (S) or Uninterruptable - // Sleep (D). - CompareProcessState("RSD", child_pid); - RETURN_IF_ERRNO(running(child_pid)); - } - - // Kill the process. - kill_cleanup.Release()(); - siginfo_t info; - // Wait until the child process has exited (WEXITED flag) but don't - // reap the child (WNOWAIT flag). - EXPECT_THAT(waitid(P_PID, child_pid, &info, WNOWAIT | WEXITED), - SyscallSucceeds()); - - if (zombied) { - // Arg of "Z" refers to a Zombied Process. - CompareProcessState("Z", child_pid); - RETURN_IF_ERRNO(zombied(child_pid)); - } - - // Wait on the process. - wait_cleanup.Release()(); - // If the process is reaped, then then this should return - // with ECHILD. - EXPECT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallFailsWithErrno(ECHILD)); - - if (exited) { - RETURN_IF_ERRNO(exited(child_pid)); - } - - return NoError(); -} - -// Access the file returned by name when a subprocess is running. -PosixError AccessWhileRunning(std::function<std::string(int pid)> name, - int flags, std::function<void(int fd)> access) { - FileDescriptor fd; - return WithSubprocess( - [&](int pid) -> PosixError { - // Running. - ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags)); - - access(fd.get()); - return NoError(); - }, - nullptr, nullptr); -} - -// Access the file returned by name when the a subprocess is zombied. -PosixError AccessWhileZombied(std::function<std::string(int pid)> name, - int flags, std::function<void(int fd)> access) { - FileDescriptor fd; - return WithSubprocess( - [&](int pid) -> PosixError { - // Running. - ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags)); - return NoError(); - }, - [&](int pid) -> PosixError { - // Zombied. - access(fd.get()); - return NoError(); - }, - nullptr); -} - -// Access the file returned by name when the a subprocess is exited. -PosixError AccessWhileExited(std::function<std::string(int pid)> name, - int flags, std::function<void(int fd)> access) { - FileDescriptor fd; - return WithSubprocess( - [&](int pid) -> PosixError { - // Running. - ASSIGN_OR_RETURN_ERRNO(fd, Open(name(pid), flags)); - return NoError(); - }, - nullptr, - [&](int pid) -> PosixError { - // Exited. - access(fd.get()); - return NoError(); - }); -} - -// ReadFd(fd=/proc/PID/basename) while PID is running. -int ReadWhileRunning(std::string const& basename, void* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileRunning( - [&](int pid) -> std::string { - return absl::StrCat("/proc/", pid, "/", basename); - }, - O_RDONLY, - [&](int fd) { - ret = ReadFd(fd, buf, count); - err = errno; - })); - errno = err; - return ret; -} - -// ReadFd(fd=/proc/PID/basename) while PID is zombied. -int ReadWhileZombied(std::string const& basename, void* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileZombied( - [&](int pid) -> std::string { - return absl::StrCat("/proc/", pid, "/", basename); - }, - O_RDONLY, - [&](int fd) { - ret = ReadFd(fd, buf, count); - err = errno; - })); - errno = err; - return ret; -} - -// ReadFd(fd=/proc/PID/basename) while PID is exited. -int ReadWhileExited(std::string const& basename, void* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileExited( - [&](int pid) -> std::string { - return absl::StrCat("/proc/", pid, "/", basename); - }, - O_RDONLY, - [&](int fd) { - ret = ReadFd(fd, buf, count); - err = errno; - })); - errno = err; - return ret; -} - -// readlinkat(fd=/proc/PID/, basename) while PID is running. -int ReadlinkWhileRunning(std::string const& basename, char* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileRunning( - [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); }, - O_DIRECTORY, - [&](int fd) { - ret = readlinkat(fd, basename.c_str(), buf, count); - err = errno; - })); - errno = err; - return ret; -} - -// readlinkat(fd=/proc/PID/, basename) while PID is zombied. -int ReadlinkWhileZombied(std::string const& basename, char* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileZombied( - [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); }, - O_DIRECTORY, - [&](int fd) { - ret = readlinkat(fd, basename.c_str(), buf, count); - err = errno; - })); - errno = err; - return ret; -} - -// readlinkat(fd=/proc/PID/, basename) while PID is exited. -int ReadlinkWhileExited(std::string const& basename, char* buf, size_t count) { - int ret = 0; - int err = 0; - EXPECT_NO_ERRNO(AccessWhileExited( - [&](int pid) -> std::string { return absl::StrCat("/proc/", pid, "/"); }, - O_DIRECTORY, - [&](int fd) { - ret = readlinkat(fd, basename.c_str(), buf, count); - err = errno; - })); - errno = err; - return ret; -} - -TEST(ProcTest, NotFoundInRoot) { - struct stat s; - EXPECT_THAT(stat("/proc/foobar", &s), SyscallFailsWithErrno(ENOENT)); -} - -TEST(ProcSelfTest, IsThreadGroupLeader) { - ScopedThread([] { - const pid_t tgid = getpid(); - const pid_t tid = syscall(SYS_gettid); - EXPECT_NE(tgid, tid); - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self")); - EXPECT_EQ(link, absl::StrCat(tgid)); - }); -} - -TEST(ProcThreadSelfTest, Basic) { - const pid_t tgid = getpid(); - const pid_t tid = syscall(SYS_gettid); - EXPECT_EQ(tgid, tid); - auto link_threadself = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self")); - EXPECT_EQ(link_threadself, absl::StrCat(tgid, "/task/", tid)); - // Just read one file inside thread-self to ensure that the link is valid. - auto link_threadself_exe = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self/exe")); - auto link_procself_exe = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); - EXPECT_EQ(link_threadself_exe, link_procself_exe); -} - -TEST(ProcThreadSelfTest, Thread) { - ScopedThread([] { - const pid_t tgid = getpid(); - const pid_t tid = syscall(SYS_gettid); - EXPECT_NE(tgid, tid); - auto link_threadself = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self")); - - EXPECT_EQ(link_threadself, absl::StrCat(tgid, "/task/", tid)); - // Just read one file inside thread-self to ensure that the link is valid. - auto link_threadself_exe = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/thread-self/exe")); - auto link_procself_exe = - ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); - EXPECT_EQ(link_threadself_exe, link_procself_exe); - // A thread should not have "/proc/<tid>/task". - struct stat s; - EXPECT_THAT(stat("/proc/thread-self/task", &s), - SyscallFailsWithErrno(ENOENT)); - }); -} - -// Returns the /proc/PID/maps entry for the MAP_PRIVATE | MAP_ANONYMOUS mapping -// m with start address addr and length len. -std::string AnonymousMapsEntry(uintptr_t addr, size_t len, int prot) { - return absl::StrCat(absl::Hex(addr, absl::PadSpec::kZeroPad8), "-", - absl::Hex(addr + len, absl::PadSpec::kZeroPad8), " ", - prot & PROT_READ ? "r" : "-", - prot & PROT_WRITE ? "w" : "-", - prot & PROT_EXEC ? "x" : "-", "p 00000000 00:00 0 "); -} - -std::string AnonymousMapsEntryForMapping(const Mapping& m, int prot) { - return AnonymousMapsEntry(m.addr(), m.len(), prot); -} - -PosixErrorOr<std::map<uint64_t, uint64_t>> ReadProcSelfAuxv() { - std::string auxv_file; - RETURN_IF_ERRNO(GetContents("/proc/self/auxv", &auxv_file)); - const Elf64_auxv_t* auxv_data = - reinterpret_cast<const Elf64_auxv_t*>(auxv_file.data()); - std::map<uint64_t, uint64_t> auxv_entries; - for (int i = 0; auxv_data[i].a_type != AT_NULL; i++) { - auto a_type = auxv_data[i].a_type; - EXPECT_EQ(0, auxv_entries.count(a_type)) << "a_type: " << a_type; - auxv_entries.emplace(a_type, auxv_data[i].a_un.a_val); - } - return auxv_entries; -} - -TEST(ProcSelfAuxv, EntryPresence) { - auto auxv_entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfAuxv()); - - EXPECT_EQ(auxv_entries.count(AT_ENTRY), 1); - EXPECT_EQ(auxv_entries.count(AT_PHDR), 1); - EXPECT_EQ(auxv_entries.count(AT_PHENT), 1); - EXPECT_EQ(auxv_entries.count(AT_PHNUM), 1); - EXPECT_EQ(auxv_entries.count(AT_BASE), 1); - EXPECT_EQ(auxv_entries.count(AT_UID), 1); - EXPECT_EQ(auxv_entries.count(AT_EUID), 1); - EXPECT_EQ(auxv_entries.count(AT_GID), 1); - EXPECT_EQ(auxv_entries.count(AT_EGID), 1); - EXPECT_EQ(auxv_entries.count(AT_SECURE), 1); - EXPECT_EQ(auxv_entries.count(AT_CLKTCK), 1); - EXPECT_EQ(auxv_entries.count(AT_RANDOM), 1); - EXPECT_EQ(auxv_entries.count(AT_EXECFN), 1); - EXPECT_EQ(auxv_entries.count(AT_PAGESZ), 1); - EXPECT_EQ(auxv_entries.count(AT_SYSINFO_EHDR), 1); -} - -TEST(ProcSelfAuxv, EntryValues) { - auto proc_auxv = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfAuxv()); - - // We need to find the ELF auxiliary vector. The section of memory pointed to - // by envp contains some pointers to non-null pointers, followed by a single - // pointer to a null pointer, followed by the auxiliary vector. - char** envpi = environ; - while (*envpi) { - ++envpi; - } - - const Elf64_auxv_t* envp_auxv = - reinterpret_cast<const Elf64_auxv_t*>(envpi + 1); - int i; - for (i = 0; envp_auxv[i].a_type != AT_NULL; i++) { - auto a_type = envp_auxv[i].a_type; - EXPECT_EQ(proc_auxv.count(a_type), 1); - EXPECT_EQ(proc_auxv[a_type], envp_auxv[i].a_un.a_val) - << "a_type: " << a_type; - } - EXPECT_EQ(i, proc_auxv.size()); -} - -// Just open and read a part of /proc/self/mem, check that we can read an item. -TEST(ProcPidMem, Read) { - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); - char input[] = "hello-world"; - char output[sizeof(input)]; - ASSERT_THAT(pread(memfd.get(), output, sizeof(output), - reinterpret_cast<off_t>(input)), - SyscallSucceedsWithValue(sizeof(input))); - ASSERT_STREQ(input, output); -} - -// Perform read on an unmapped region. -TEST(ProcPidMem, Unmapped) { - // Strategy: map then unmap, so we have a guaranteed unmapped region - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); - Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - // Fill it with things - memset(mapping.ptr(), 'x', mapping.len()); - char expected = 'x', output; - ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), - reinterpret_cast<off_t>(mapping.ptr())), - SyscallSucceedsWithValue(sizeof(output))); - ASSERT_EQ(expected, output); - - // Unmap region again - ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); - - // Now we want EIO error - ASSERT_THAT(pread(memfd.get(), &output, sizeof(output), - reinterpret_cast<off_t>(mapping.ptr())), - SyscallFailsWithErrno(EIO)); -} - -// Perform read repeatedly to verify offset change. -TEST(ProcPidMem, RepeatedRead) { - auto const num_reads = 3; - char expected[] = "01234567890abcdefghijkl"; - char output[sizeof(expected) / num_reads]; - - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); - ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), - SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); - for (auto i = 0; i < num_reads; i++) { - ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), - SyscallSucceedsWithValue(sizeof(output))); - ASSERT_EQ(strncmp(&expected[i * sizeof(output)], output, sizeof(output)), - 0); - } -} - -// Perform seek operations repeatedly. -TEST(ProcPidMem, RepeatedSeek) { - auto const num_reads = 3; - char expected[] = "01234567890abcdefghijkl"; - char output[sizeof(expected) / num_reads]; - - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); - ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET), - SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); - // Read from start - ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), - SyscallSucceedsWithValue(sizeof(output))); - ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); - // Skip ahead one read - ASSERT_THAT(lseek(memfd.get(), sizeof(output), SEEK_CUR), - SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected) + - sizeof(output) * 2)); - // Do read again - ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), - SyscallSucceedsWithValue(sizeof(output))); - ASSERT_EQ(strncmp(&expected[2 * sizeof(output)], output, sizeof(output)), 0); - // Skip back three reads - ASSERT_THAT(lseek(memfd.get(), -3 * sizeof(output), SEEK_CUR), - SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected))); - // Do read again - ASSERT_THAT(read(memfd.get(), &output, sizeof(output)), - SyscallSucceedsWithValue(sizeof(output))); - ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0); - // Check that SEEK_END does not work - ASSERT_THAT(lseek(memfd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL)); -} - -// Perform read past an allocated memory region. -TEST(ProcPidMem, PartialRead) { - // Strategy: map large region, then do unmap and remap smaller region - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY)); - - Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds()); - Mapping smaller_mapping = ASSERT_NO_ERRNO_AND_VALUE( - Mmap(mapping.ptr(), kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - - // Fill it with things - memset(smaller_mapping.ptr(), 'x', smaller_mapping.len()); - - // Now we want no error - char expected[] = {'x'}; - std::unique_ptr<char[]> output(new char[kPageSize]); - off_t read_offset = - reinterpret_cast<off_t>(smaller_mapping.ptr()) + kPageSize - 1; - ASSERT_THAT( - pread(memfd.get(), output.get(), sizeof(output.get()), read_offset), - SyscallSucceedsWithValue(sizeof(expected))); - // Since output is larger, than expected we have to do manual compare - ASSERT_EQ(expected[0], (output).get()[0]); -} - -// Perform read on /proc/[pid]/mem after exit. -TEST(ProcPidMem, AfterExit) { - int pfd1[2] = {}; - int pfd2[2] = {}; - - char expected[] = "hello-world"; - - ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); - ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); - - // Create child process - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Close reading end of first pipe - close(pfd1[0]); - - // Tell parent about location of input - char ok = 1; - TEST_CHECK(WriteFd(pfd1[1], &ok, sizeof(ok)) == sizeof(ok)); - TEST_PCHECK(close(pfd1[1]) == 0); - - // Close writing end of second pipe - TEST_PCHECK(close(pfd2[1]) == 0); - - // Await parent OK to die - ok = 0; - TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); - - // Close rest pipes - TEST_PCHECK(close(pfd2[0]) == 0); - _exit(0); - } - - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Close writing end of first pipe - EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); - - // Wait for child to be alive and well - char ok = 0; - EXPECT_THAT(ReadFd(pfd1[0], &ok, sizeof(ok)), - SyscallSucceedsWithValue(sizeof(ok))); - // Close reading end of first pipe - EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); - - // Open /proc/pid/mem fd - std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); - - // Expect that we can read - char output[sizeof(expected)]; - EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), - reinterpret_cast<off_t>(&expected)), - SyscallSucceedsWithValue(sizeof(output))); - EXPECT_STREQ(expected, output); - - // Tell proc its ok to go - EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); - ok = 1; - EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), - SyscallSucceedsWithValue(sizeof(ok))); - EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); - - // Expect termination - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - - // Expect that we can't read anymore - EXPECT_THAT(pread(memfd.get(), &output, sizeof(output), - reinterpret_cast<off_t>(&expected)), - SyscallSucceedsWithValue(0)); -} - -// Read from /proc/[pid]/mem with different UID/GID and attached state. -TEST(ProcPidMem, DifferentUserAttached) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_DAC_OVERRIDE))); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE))); - - int pfd1[2] = {}; - int pfd2[2] = {}; - - ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); - ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); - - // Create child process - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Close reading end of first pipe - close(pfd1[0]); - - // Tell parent about location of input - char input[] = "hello-world"; - off_t input_location = reinterpret_cast<off_t>(input); - TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == - sizeof(input_location)); - TEST_PCHECK(close(pfd1[1]) == 0); - - // Close writing end of second pipe - TEST_PCHECK(close(pfd2[1]) == 0); - - // Await parent OK to die - char ok = 0; - TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); - - // Close rest pipes - TEST_PCHECK(close(pfd2[0]) == 0); - _exit(0); - } - - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Close writing end of first pipe - EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); - - // Read target location from child - off_t target_location; - EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), - SyscallSucceedsWithValue(sizeof(target_location))); - // Close reading end of first pipe - EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); - - ScopedThread([&] { - // Attach to child subprocess without stopping it - EXPECT_THAT(ptrace(PTRACE_SEIZE, child_pid, NULL, NULL), SyscallSucceeds()); - - // Keep capabilities after setuid - EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); - constexpr int kNobody = 65534; - EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); - - // Only restore CAP_SYS_PTRACE and CAP_DAC_OVERRIDE - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, true)); - EXPECT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, true)); - - // Open /proc/pid/mem fd - std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); - char expected[] = "hello-world"; - char output[sizeof(expected)]; - EXPECT_THAT(pread(memfd.get(), output, sizeof(output), - reinterpret_cast<off_t>(target_location)), - SyscallSucceedsWithValue(sizeof(output))); - EXPECT_STREQ(expected, output); - - // Tell proc its ok to go - EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); - char ok = 1; - EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), - SyscallSucceedsWithValue(sizeof(ok))); - EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); - - // Expect termination - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - }); -} - -// Attempt to read from /proc/[pid]/mem with different UID/GID. -TEST(ProcPidMem, DifferentUser) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - int pfd1[2] = {}; - int pfd2[2] = {}; - - ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); - ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); - - // Create child process - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Close reading end of first pipe - close(pfd1[0]); - - // Tell parent about location of input - char input[] = "hello-world"; - off_t input_location = reinterpret_cast<off_t>(input); - TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == - sizeof(input_location)); - TEST_PCHECK(close(pfd1[1]) == 0); - - // Close writing end of second pipe - TEST_PCHECK(close(pfd2[1]) == 0); - - // Await parent OK to die - char ok = 0; - TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); - - // Close rest pipes - TEST_PCHECK(close(pfd2[0]) == 0); - _exit(0); - } - - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Close writing end of first pipe - EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); - - // Read target location from child - off_t target_location; - EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), - SyscallSucceedsWithValue(sizeof(target_location))); - // Close reading end of first pipe - EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); - - ScopedThread([&] { - constexpr int kNobody = 65534; - EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); - - // Attempt to open /proc/[child_pid]/mem - std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); - EXPECT_THAT(open(mempath.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); - - // Tell proc its ok to go - EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); - char ok = 1; - EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), - SyscallSucceedsWithValue(sizeof(ok))); - EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); - - // Expect termination - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - }); -} - -// Perform read on /proc/[pid]/mem with same UID/GID. -TEST(ProcPidMem, SameUser) { - int pfd1[2] = {}; - int pfd2[2] = {}; - - ASSERT_THAT(pipe(pfd1), SyscallSucceeds()); - ASSERT_THAT(pipe(pfd2), SyscallSucceeds()); - - // Create child process - pid_t const child_pid = fork(); - if (child_pid == 0) { - // Close reading end of first pipe - close(pfd1[0]); - - // Tell parent about location of input - char input[] = "hello-world"; - off_t input_location = reinterpret_cast<off_t>(input); - TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) == - sizeof(input_location)); - TEST_PCHECK(close(pfd1[1]) == 0); - - // Close writing end of second pipe - TEST_PCHECK(close(pfd2[1]) == 0); - - // Await parent OK to die - char ok = 0; - TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok)); - - // Close rest pipes - TEST_PCHECK(close(pfd2[0]) == 0); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Close writing end of first pipe - EXPECT_THAT(close(pfd1[1]), SyscallSucceeds()); - - // Read target location from child - off_t target_location; - EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)), - SyscallSucceedsWithValue(sizeof(target_location))); - // Close reading end of first pipe - EXPECT_THAT(close(pfd1[0]), SyscallSucceeds()); - - // Open /proc/pid/mem fd - std::string mempath = absl::StrCat("/proc/", child_pid, "/mem"); - auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY)); - char expected[] = "hello-world"; - char output[sizeof(expected)]; - EXPECT_THAT(pread(memfd.get(), output, sizeof(output), - reinterpret_cast<off_t>(target_location)), - SyscallSucceedsWithValue(sizeof(output))); - EXPECT_STREQ(expected, output); - - // Tell proc its ok to go - EXPECT_THAT(close(pfd2[0]), SyscallSucceeds()); - char ok = 1; - EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)), - SyscallSucceedsWithValue(sizeof(ok))); - EXPECT_THAT(close(pfd2[1]), SyscallSucceeds()); - - // Expect termination - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); -} - -// Just open and read /proc/self/maps, check that we can find [stack] -TEST(ProcSelfMaps, Basic) { - auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - - std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n'); - std::vector<std::string> stacks; - // Make sure there's a stack in there. - for (const auto& str : strings) { - if (str.find("[stack]") != std::string::npos) { - stacks.push_back(str); - } - } - ASSERT_EQ(1, stacks.size()) << "[stack] not found in: " << proc_self_maps; - // Linux pads to 73 characters then we add 7. - EXPECT_EQ(80, stacks[0].length()); -} - -TEST(ProcSelfMaps, Map1) { - Mapping mapping = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_READ, MAP_PRIVATE)); - auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n'); - std::vector<std::string> addrs; - // Make sure if is listed. - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(mapping, PROT_READ)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()); -} - -TEST(ProcSelfMaps, Map2) { - // NOTE(magi): The permissions must be different or the pages will get merged. - Mapping map1 = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_EXEC, MAP_PRIVATE)); - Mapping map2 = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_WRITE, MAP_PRIVATE)); - - auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n'); - std::vector<std::string> addrs; - // Make sure if is listed. - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()); - addrs.clear(); - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()); -} - -TEST(ProcSelfMaps, MapUnmap) { - Mapping map1 = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ | PROT_EXEC, MAP_PRIVATE)); - Mapping map2 = - ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_WRITE, MAP_PRIVATE)); - - auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n'); - std::vector<std::string> addrs; - // Make sure if is listed. - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()) << proc_self_maps; - addrs.clear(); - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()); - - map2.reset(); - - // Read it again. - proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - strings = absl::StrSplit(proc_self_maps, '\n'); - // First entry should be there. - addrs.clear(); - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map1, PROT_READ | PROT_EXEC)) { - addrs.push_back(str); - } - } - ASSERT_EQ(1, addrs.size()); - addrs.clear(); - // But not the second. - for (const auto& str : strings) { - if (str == AnonymousMapsEntryForMapping(map2, PROT_WRITE)) { - addrs.push_back(str); - } - } - ASSERT_EQ(0, addrs.size()); -} - -TEST(ProcSelfMaps, Mprotect) { - // FIXME(jamieliu): Linux's mprotect() sometimes fails to merge VMAs in this - // case. - SKIP_IF(!IsRunningOnGvisor()); - - // Reserve 5 pages of address space. - Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(5 * kPageSize, PROT_NONE, MAP_PRIVATE)); - - // Change the permissions on the middle 3 pages. (The first and last pages may - // be merged with other vmas on either side, so they aren't tested directly; - // they just ensure that the middle 3 pages are bracketed by VMAs with - // incompatible permissions.) - ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + kPageSize), - 3 * kPageSize, PROT_READ), - SyscallSucceeds()); - - // Check that the middle 3 pages make up a single VMA. - auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - std::vector<std::string> strings = absl::StrSplit(proc_self_maps, '\n'); - EXPECT_THAT(strings, Contains(AnonymousMapsEntry(m.addr() + kPageSize, - 3 * kPageSize, PROT_READ))); - - // Change the permissions on the middle page only. - ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + 2 * kPageSize), - kPageSize, PROT_READ | PROT_WRITE), - SyscallSucceeds()); - - // Check that the single VMA has been split into 3 VMAs. - proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - strings = absl::StrSplit(proc_self_maps, '\n'); - EXPECT_THAT( - strings, - IsSupersetOf( - {AnonymousMapsEntry(m.addr() + kPageSize, kPageSize, PROT_READ), - AnonymousMapsEntry(m.addr() + 2 * kPageSize, kPageSize, - PROT_READ | PROT_WRITE), - AnonymousMapsEntry(m.addr() + 3 * kPageSize, kPageSize, - PROT_READ)})); - - // Change the permissions on the middle page back. - ASSERT_THAT(mprotect(reinterpret_cast<void*>(m.addr() + 2 * kPageSize), - kPageSize, PROT_READ), - SyscallSucceeds()); - - // Check that the 3 VMAs have been merged back into a single VMA. - proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - strings = absl::StrSplit(proc_self_maps, '\n'); - EXPECT_THAT(strings, Contains(AnonymousMapsEntry(m.addr() + kPageSize, - 3 * kPageSize, PROT_READ))); -} - -TEST(ProcSelfMaps, SharedAnon) { - const Mapping m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kPageSize, PROT_READ, MAP_SHARED | MAP_ANONYMOUS)); - - const auto proc_self_maps = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - for (const auto& line : absl::StrSplit(proc_self_maps, '\n')) { - const auto entry = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMapsLine(line)); - if (entry.start <= m.addr() && m.addr() < entry.end) { - // cf. proc(5), "/proc/[pid]/map_files/" - EXPECT_EQ(entry.filename, "/dev/zero (deleted)"); - return; - } - } - FAIL() << "no maps entry containing mapping at " << m.ptr(); -} - -TEST(ProcSelfFd, OpenFd) { - int pipe_fds[2]; - ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds()); - - // Reopen the write end. - const std::string path = absl::StrCat("/proc/self/fd/", pipe_fds[1]); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_WRONLY)); - - // Ensure that a read/write works. - const std::string data = "hello"; - std::unique_ptr<char[]> buffer(new char[data.size()]); - EXPECT_THAT(write(fd.get(), data.c_str(), data.size()), - SyscallSucceedsWithValue(5)); - EXPECT_THAT(read(pipe_fds[0], buffer.get(), data.size()), - SyscallSucceedsWithValue(5)); - EXPECT_EQ(strncmp(buffer.get(), data.c_str(), data.size()), 0); - - // Cleanup. - ASSERT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - ASSERT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -static void CheckFdDirGetdentsDuplicates(const std::string& path) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_RDONLY | O_DIRECTORY)); - // Open a FD whose value is supposed to be much larger than - // the number of FDs opened by current process. - auto newfd = fcntl(fd.get(), F_DUPFD, 1024); - EXPECT_GE(newfd, 1024); - auto fd_closer = Cleanup([newfd]() { close(newfd); }); - auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir(path.c_str(), false)); - absl::node_hash_set<std::string> fd_files_dedup(fd_files.begin(), - fd_files.end()); - EXPECT_EQ(fd_files.size(), fd_files_dedup.size()); -} - -// This is a regression test for gvisor.dev/issues/3894 -TEST(ProcSelfFd, GetdentsDuplicates) { - CheckFdDirGetdentsDuplicates("/proc/self/fd"); -} - -// This is a regression test for gvisor.dev/issues/3894 -TEST(ProcSelfFdInfo, GetdentsDuplicates) { - CheckFdDirGetdentsDuplicates("/proc/self/fdinfo"); -} - -TEST(ProcSelfFdInfo, CorrectFds) { - // Make sure there is at least one open file. - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); - - // Get files in /proc/self/fd. - auto fd_files = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/fd", false)); - - // Get files in /proc/self/fdinfo. - auto fdinfo_files = - ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/fdinfo", false)); - - // They should contain the same fds. - EXPECT_THAT(fd_files, UnorderedElementsAreArray(fdinfo_files)); - - // Both should contain fd. - auto fd_s = absl::StrCat(fd.get()); - EXPECT_THAT(fd_files, Contains(fd_s)); -} - -TEST(ProcSelfFdInfo, Flags) { - std::string path = NewTempAbsPath(); - - // Create file here with O_CREAT to test that O_CREAT does not appear in - // fdinfo flags. - int flags = O_CREAT | O_RDWR | O_APPEND | O_CLOEXEC; - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, flags, 0644)); - - // Automatically delete path. - TempPath temp_path(path); - - // O_CREAT does not appear in fdinfo flags. - flags &= ~O_CREAT; - - // O_LARGEFILE always appears (on x86_64). - flags |= kOLargeFile; - - auto fd_info = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/self/fdinfo/", fd.get()))); - EXPECT_THAT(fd_info, HasSubstr(absl::StrFormat("flags:\t%#o", flags))); -} - -TEST(ProcSelfExe, Absolute) { - auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); - EXPECT_EQ(exe[0], '/'); -} - -TEST(ProcSelfCwd, Absolute) { - auto exe = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/cwd")); - EXPECT_EQ(exe[0], '/'); -} - -// Sanity check for /proc/cpuinfo fields that must be present. -TEST(ProcCpuinfo, RequiredFieldsArePresent) { - std::string proc_cpuinfo = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo")); - ASSERT_FALSE(proc_cpuinfo.empty()); - std::vector<std::string> cpuinfo_fields = absl::StrSplit(proc_cpuinfo, '\n'); - - // Check that the usual fields are there. We don't really care about the - // contents. - for (const std::string& field : required_fields) { - EXPECT_THAT(proc_cpuinfo, HasSubstr(field)); - } -} - -TEST(ProcCpuinfo, DeniesWriteNonRoot) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER))); - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. After calling - // setuid(non-zero-UID), there is no way to get root privileges back. - ScopedThread([&] { - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. POSIX threads, however, require that all - // threads have the same UIDs, so using the setuid wrapper sets all threads' - // real UID. - // Also drops capabilities. - constexpr int kNobody = 65534; - EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds()); - EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EACCES)); - }); -} - -// With root privileges, it is possible to open /proc/cpuinfo with write mode, -// but all write operations should fail. -TEST(ProcCpuinfo, DeniesWriteRoot) { - // VFS1 does not behave differently for root/non-root. - SKIP_IF(IsRunningWithVFS1()); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER))); - - int fd; - EXPECT_THAT(fd = open("/proc/cpuinfo", O_WRONLY), SyscallSucceeds()); - if (fd > 0) { - // Truncate is not tested--it may succeed on some kernels without doing - // anything. - EXPECT_THAT(write(fd, "x", 1), SyscallFails()); - EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFails()); - } -} - -// Sanity checks that uptime is present. -TEST(ProcUptime, IsPresent) { - std::string proc_uptime = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/uptime")); - ASSERT_FALSE(proc_uptime.empty()); - std::vector<std::string> uptime_parts = absl::StrSplit(proc_uptime, ' '); - - // Parse once. - double uptime0, uptime1, idletime0, idletime1; - ASSERT_TRUE(absl::SimpleAtod(uptime_parts[0], &uptime0)); - ASSERT_TRUE(absl::SimpleAtod(uptime_parts[1], &idletime0)); - - // Sleep for one second. - absl::SleepFor(absl::Seconds(1)); - - // Parse again. - proc_uptime = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/uptime")); - ASSERT_FALSE(proc_uptime.empty()); - uptime_parts = absl::StrSplit(proc_uptime, ' '); - ASSERT_TRUE(absl::SimpleAtod(uptime_parts[0], &uptime1)); - ASSERT_TRUE(absl::SimpleAtod(uptime_parts[1], &idletime1)); - - // Sanity check. - // - // We assert that between 0.99 and 59.99 seconds have passed. If more than a - // minute has passed, then we must be executing really, really slowly. - EXPECT_GE(uptime0, 0.0); - EXPECT_GE(idletime0, 0.0); - EXPECT_GT(uptime1, uptime0); - EXPECT_GE(uptime1, uptime0 + 0.99); - EXPECT_LE(uptime1, uptime0 + 59.99); - EXPECT_GE(idletime1, idletime0); -} - -TEST(ProcMeminfo, ContainsBasicFields) { - std::string proc_meminfo = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/meminfo")); - EXPECT_THAT(proc_meminfo, AllOf(ContainsRegex(R"(MemTotal:\s+[0-9]+ kB)"), - ContainsRegex(R"(MemFree:\s+[0-9]+ kB)"))); -} - -TEST(ProcStat, ContainsBasicFields) { - std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); - - std::vector<std::string> names; - for (auto const& line : absl::StrSplit(proc_stat, '\n')) { - std::vector<std::string> fields = - absl::StrSplit(line, ' ', absl::SkipWhitespace()); - if (fields.empty()) { - continue; - } - names.push_back(fields[0]); - } - - EXPECT_THAT(names, - IsSupersetOf({"cpu", "intr", "ctxt", "btime", "processes", - "procs_running", "procs_blocked", "softirq"})); -} - -TEST(ProcStat, EndsWithNewline) { - std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); - EXPECT_EQ(proc_stat.back(), '\n'); -} - -TEST(ProcStat, Fields) { - std::string proc_stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); - - std::vector<std::string> names; - for (auto const& line : absl::StrSplit(proc_stat, '\n')) { - std::vector<std::string> fields = - absl::StrSplit(line, ' ', absl::SkipWhitespace()); - if (fields.empty()) { - continue; - } - - if (absl::StartsWith(fields[0], "cpu")) { - // As of Linux 3.11, each CPU entry has 10 fields, plus the name. - EXPECT_GE(fields.size(), 11) << proc_stat; - } else if (fields[0] == "ctxt") { - // Single field. - EXPECT_EQ(fields.size(), 2) << proc_stat; - } else if (fields[0] == "btime") { - // Single field. - EXPECT_EQ(fields.size(), 2) << proc_stat; - } else if (fields[0] == "itime") { - // Single field. - ASSERT_EQ(fields.size(), 2) << proc_stat; - // This is the only floating point field. - double val; - EXPECT_TRUE(absl::SimpleAtod(fields[1], &val)) << proc_stat; - continue; - } else if (fields[0] == "processes") { - // Single field. - EXPECT_EQ(fields.size(), 2) << proc_stat; - } else if (fields[0] == "procs_running") { - // Single field. - EXPECT_EQ(fields.size(), 2) << proc_stat; - } else if (fields[0] == "procs_blocked") { - // Single field. - EXPECT_EQ(fields.size(), 2) << proc_stat; - } else if (fields[0] == "softirq") { - // As of Linux 3.11, there are 10 softirqs. 12 fields for name + total. - EXPECT_GE(fields.size(), 12) << proc_stat; - } - - // All fields besides itime are valid base 10 numbers. - for (size_t i = 1; i < fields.size(); i++) { - uint64_t val; - EXPECT_TRUE(absl::SimpleAtoi(fields[i], &val)) << proc_stat; - } - } -} - -TEST(ProcLoadavg, EndsWithNewline) { - std::string proc_loadvg = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/loadavg")); - EXPECT_EQ(proc_loadvg.back(), '\n'); -} - -TEST(ProcLoadavg, Fields) { - std::string proc_loadvg = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/loadavg")); - std::vector<std::string> lines = absl::StrSplit(proc_loadvg, '\n'); - - // Single line. - EXPECT_EQ(lines.size(), 2) << proc_loadvg; - - std::vector<std::string> fields = - absl::StrSplit(lines[0], absl::ByAnyChar(" /"), absl::SkipWhitespace()); - - // Six fields. - EXPECT_EQ(fields.size(), 6) << proc_loadvg; - - double val; - uint64_t val2; - // First three fields are floating point numbers. - EXPECT_TRUE(absl::SimpleAtod(fields[0], &val)) << proc_loadvg; - EXPECT_TRUE(absl::SimpleAtod(fields[1], &val)) << proc_loadvg; - EXPECT_TRUE(absl::SimpleAtod(fields[2], &val)) << proc_loadvg; - // Rest of the fields are valid base 10 numbers. - EXPECT_TRUE(absl::SimpleAtoi(fields[3], &val2)) << proc_loadvg; - EXPECT_TRUE(absl::SimpleAtoi(fields[4], &val2)) << proc_loadvg; - EXPECT_TRUE(absl::SimpleAtoi(fields[5], &val2)) << proc_loadvg; -} - -// NOTE: Tests in priority.cc also check certain priority related fields in -// /proc/self/stat. - -class ProcPidStatTest : public ::testing::TestWithParam<std::string> {}; - -TEST_P(ProcPidStatTest, HasBasicFields) { - std::string proc_pid_stat = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/", GetParam(), "/stat"))); - - ASSERT_FALSE(proc_pid_stat.empty()); - std::vector<std::string> fields = absl::StrSplit(proc_pid_stat, ' '); - ASSERT_GE(fields.size(), 24); - EXPECT_EQ(absl::StrCat(getpid()), fields[0]); - // fields[1] is the thread name. - EXPECT_EQ("R", fields[2]); // task state - EXPECT_EQ(absl::StrCat(getppid()), fields[3]); - - // If the test starts up quickly, then the process start time and the kernel - // boot time will be very close, and the proc starttime field (which is the - // delta of the two times) will be 0. For that unfortunate reason, we can - // only check that starttime >= 0, and not that it is strictly > 0. - uint64_t starttime; - ASSERT_TRUE(absl::SimpleAtoi(fields[21], &starttime)); - EXPECT_GE(starttime, 0); - - uint64_t vss; - ASSERT_TRUE(absl::SimpleAtoi(fields[22], &vss)); - EXPECT_GT(vss, 0); - - uint64_t rss; - ASSERT_TRUE(absl::SimpleAtoi(fields[23], &rss)); - EXPECT_GT(rss, 0); - - uint64_t rsslim; - ASSERT_TRUE(absl::SimpleAtoi(fields[24], &rsslim)); - EXPECT_GT(rsslim, 0); -} - -INSTANTIATE_TEST_SUITE_P(SelfAndNumericPid, ProcPidStatTest, - ::testing::Values("self", absl::StrCat(getpid()))); - -using ProcPidStatmTest = ::testing::TestWithParam<std::string>; - -TEST_P(ProcPidStatmTest, HasBasicFields) { - std::string proc_pid_statm = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/", GetParam(), "/statm"))); - ASSERT_FALSE(proc_pid_statm.empty()); - std::vector<std::string> fields = absl::StrSplit(proc_pid_statm, ' '); - ASSERT_GE(fields.size(), 7); - - uint64_t vss; - ASSERT_TRUE(absl::SimpleAtoi(fields[0], &vss)); - EXPECT_GT(vss, 0); - - uint64_t rss; - ASSERT_TRUE(absl::SimpleAtoi(fields[1], &rss)); - EXPECT_GT(rss, 0); -} - -INSTANTIATE_TEST_SUITE_P(SelfAndNumericPid, ProcPidStatmTest, - ::testing::Values("self", absl::StrCat(getpid()))); - -PosixErrorOr<uint64_t> CurrentRSS() { - ASSIGN_OR_RETURN_ERRNO(auto proc_self_stat, GetContents("/proc/self/stat")); - if (proc_self_stat.empty()) { - return PosixError(EINVAL, "empty /proc/self/stat"); - } - - std::vector<std::string> fields = absl::StrSplit(proc_self_stat, ' '); - if (fields.size() < 24) { - return PosixError( - EINVAL, - absl::StrCat("/proc/self/stat has too few fields: ", proc_self_stat)); - } - - uint64_t rss; - if (!absl::SimpleAtoi(fields[23], &rss)) { - return PosixError( - EINVAL, absl::StrCat("/proc/self/stat RSS field is not a number: ", - fields[23])); - } - - // RSS is given in number of pages. - return rss * kPageSize; -} - -// The size of mapping created by MapPopulateRSS. -constexpr uint64_t kMappingSize = 100 << 20; - -// Tolerance on RSS comparisons to account for background thread mappings, -// reclaimed pages, newly faulted pages, etc. -constexpr uint64_t kRSSTolerance = 10 << 20; - -// Capture RSS before and after an anonymous mapping with passed prot. -void MapPopulateRSS(int prot, uint64_t* before, uint64_t* after) { - *before = ASSERT_NO_ERRNO_AND_VALUE(CurrentRSS()); - - // N.B. The kernel asynchronously accumulates per-task RSS counters into the - // mm RSS, which is exposed by /proc/PID/stat. Task exit is a synchronization - // point (kernel/exit.c:do_exit -> sync_mm_rss), so perform the mapping on - // another thread to ensure it is reflected in RSS after the thread exits. - Mapping mapping; - ScopedThread t([&mapping, prot] { - mapping = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(kMappingSize, prot, MAP_PRIVATE | MAP_POPULATE)); - }); - t.Join(); - - *after = ASSERT_NO_ERRNO_AND_VALUE(CurrentRSS()); -} - -// TODO(b/73896574): Test for PROT_READ + MAP_POPULATE anonymous mappings. Their -// semantics are more subtle: -// -// Small pages -> Zero page mapped, not counted in RSS -// (mm/memory.c:do_anonymous_page). -// -// Huge pages (THP enabled, use_zero_page=0) -> Pages committed -// (mm/memory.c:__handle_mm_fault -> create_huge_pmd). -// -// Huge pages (THP enabled, use_zero_page=1) -> Zero page mapped, not counted in -// RSS (mm/huge_memory.c:do_huge_pmd_anonymous_page). - -// PROT_WRITE + MAP_POPULATE anonymous mappings are always committed. -TEST(ProcSelfStat, PopulateWriteRSS) { - uint64_t before, after; - MapPopulateRSS(PROT_READ | PROT_WRITE, &before, &after); - - // Mapping is committed. - EXPECT_NEAR(before + kMappingSize, after, kRSSTolerance); -} - -// PROT_NONE + MAP_POPULATE anonymous mappings are never committed. -TEST(ProcSelfStat, PopulateNoneRSS) { - uint64_t before, after; - MapPopulateRSS(PROT_NONE, &before, &after); - - // Mapping not committed. - EXPECT_NEAR(before, after, kRSSTolerance); -} - -// Returns the calling thread's name. -PosixErrorOr<std::string> ThreadName() { - // "The buffer should allow space for up to 16 bytes; the returned std::string - // will be null-terminated if it is shorter than that." - prctl(2). But we - // always want the thread name to be null-terminated. - char thread_name[17]; - int rc = prctl(PR_GET_NAME, thread_name, 0, 0, 0); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "prctl(PR_GET_NAME)"); - } - thread_name[16] = '\0'; - return std::string(thread_name); -} - -// Parses the contents of a /proc/[pid]/status file into a collection of -// key-value pairs. -PosixErrorOr<std::map<std::string, std::string>> ParseProcStatus( - absl::string_view status_str) { - std::map<std::string, std::string> fields; - for (absl::string_view const line : - absl::StrSplit(status_str, '\n', absl::SkipWhitespace())) { - const std::pair<absl::string_view, absl::string_view> kv = - absl::StrSplit(line, absl::MaxSplits(":\t", 1)); - if (kv.first.empty()) { - return PosixError( - EINVAL, absl::StrCat("failed to parse key in line \"", line, "\"")); - } - std::string key(kv.first); - if (fields.count(key)) { - return PosixError(EINVAL, - absl::StrCat("duplicate key \"", kv.first, "\"")); - } - std::string value(kv.second); - absl::StripLeadingAsciiWhitespace(&value); - fields.emplace(std::move(key), std::move(value)); - } - return fields; -} - -TEST(ParseProcStatusTest, ParsesSimpleStatusFileWithMixedWhitespaceCorrectly) { - EXPECT_THAT( - ParseProcStatus( - "Name:\tinit\nState:\tS (sleeping)\nCapEff:\t 0000001fffffffff\n"), - IsPosixErrorOkAndHolds(UnorderedElementsAre( - Pair("Name", "init"), Pair("State", "S (sleeping)"), - Pair("CapEff", "0000001fffffffff")))); -} - -TEST(ParseProcStatusTest, DetectsDuplicateKeys) { - auto proc_status_or = ParseProcStatus("Name:\tfoo\nName:\tfoo\n"); - EXPECT_THAT(proc_status_or, - PosixErrorIs(EINVAL, ::testing::StrEq("duplicate key \"Name\""))); -} - -TEST(ParseProcStatusTest, DetectsMissingTabs) { - EXPECT_THAT(ParseProcStatus("Name:foo\nPid: 1\n"), - IsPosixErrorOkAndHolds(UnorderedElementsAre(Pair("Name:foo", ""), - Pair("Pid: 1", "")))); -} - -TEST(ProcPidStatusTest, HasBasicFields) { - // Do this on a separate thread since we want tgid != tid. - ScopedThread([] { - const pid_t tgid = getpid(); - const pid_t tid = syscall(SYS_gettid); - EXPECT_NE(tgid, tid); - const auto thread_name = ASSERT_NO_ERRNO_AND_VALUE(ThreadName()); - - std::string status_str = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/", tid, "/status"))); - - ASSERT_FALSE(status_str.empty()); - const auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str)); - EXPECT_THAT(status, IsSupersetOf({Pair("Name", thread_name), - Pair("Tgid", absl::StrCat(tgid)), - Pair("Pid", absl::StrCat(tid)), - Pair("PPid", absl::StrCat(getppid()))})); - }); -} - -TEST(ProcPidStatusTest, StateRunning) { - // Task must be running when reading the file. - const pid_t tid = syscall(SYS_gettid); - std::string status_str = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(absl::StrCat("/proc/", tid, "/status"))); - - EXPECT_THAT(ParseProcStatus(status_str), - IsPosixErrorOkAndHolds(Contains(Pair("State", "R (running)")))); -} - -TEST(ProcPidStatusTest, StateSleeping_NoRandomSave) { - // Starts a child process that blocks and checks that State is sleeping. - auto res = WithSubprocess( - [&](int pid) -> PosixError { - // Because this test is timing based we will disable cooperative saving - // and the test itself also has random saving disabled. - const DisableSave ds; - // Try multiple times in case the child isn't sleeping when status file - // is read. - MonotonicTimer timer; - timer.Start(); - for (;;) { - ASSIGN_OR_RETURN_ERRNO( - std::string status_str, - GetContents(absl::StrCat("/proc/", pid, "/status"))); - ASSIGN_OR_RETURN_ERRNO(auto map, ParseProcStatus(status_str)); - if (map["State"] == std::string("S (sleeping)")) { - // Test passed! - return NoError(); - } - if (timer.Duration() > absl::Seconds(10)) { - return PosixError(ETIMEDOUT, "Timeout waiting for child to sleep"); - } - absl::SleepFor(absl::Milliseconds(10)); - } - }, - nullptr, nullptr); - ASSERT_NO_ERRNO(res); -} - -TEST(ProcPidStatusTest, ValuesAreTabDelimited) { - std::string status_str = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/status")); - ASSERT_FALSE(status_str.empty()); - for (absl::string_view const line : - absl::StrSplit(status_str, '\n', absl::SkipWhitespace())) { - EXPECT_NE(std::string::npos, line.find(":\t")); - } -} - -// Threads properly counts running threads. -// -// TODO(mpratt): Test zombied threads while the thread group leader is still -// running with generalized fork and clone children from the wait test. -TEST(ProcPidStatusTest, Threads) { - char buf[4096] = {}; - EXPECT_THAT(ReadWhileRunning("status", buf, sizeof(buf) - 1), - SyscallSucceedsWithValue(Gt(0))); - - auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(buf)); - auto it = status.find("Threads"); - ASSERT_NE(it, status.end()); - int threads = -1; - EXPECT_TRUE(absl::SimpleAtoi(it->second, &threads)) - << "Threads value " << it->second << " is not a number"; - // Don't make assumptions about the exact number of threads, as it may not be - // constant. - EXPECT_GE(threads, 1); - - memset(buf, 0, sizeof(buf)); - EXPECT_THAT(ReadWhileZombied("status", buf, sizeof(buf) - 1), - SyscallSucceedsWithValue(Gt(0))); - - status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(buf)); - it = status.find("Threads"); - ASSERT_NE(it, status.end()); - threads = -1; - EXPECT_TRUE(absl::SimpleAtoi(it->second, &threads)) - << "Threads value " << it->second << " is not a number"; - // There must be only the thread group leader remaining, zombied. - EXPECT_EQ(threads, 1); -} - -// Returns true if all characters in s are digits. -bool IsDigits(absl::string_view s) { - return std::all_of(s.begin(), s.end(), absl::ascii_isdigit); -} - -TEST(ProcPidStatTest, VmStats) { - std::string status_str = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/status")); - ASSERT_FALSE(status_str.empty()); - auto status = ASSERT_NO_ERRNO_AND_VALUE(ParseProcStatus(status_str)); - - const auto vss_it = status.find("VmSize"); - ASSERT_NE(vss_it, status.end()); - - absl::string_view vss_str(vss_it->second); - - // Room for the " kB" suffix plus at least one digit. - ASSERT_GT(vss_str.length(), 3); - EXPECT_TRUE(absl::EndsWith(vss_str, " kB")); - // Everything else is part of a number. - EXPECT_TRUE(IsDigits(vss_str.substr(0, vss_str.length() - 3))) << vss_str; - // ... which is not 0. - EXPECT_NE('0', vss_str[0]); - - const auto rss_it = status.find("VmRSS"); - ASSERT_NE(rss_it, status.end()); - - absl::string_view rss_str(rss_it->second); - - // Room for the " kB" suffix plus at least one digit. - ASSERT_GT(rss_str.length(), 3); - EXPECT_TRUE(absl::EndsWith(rss_str, " kB")); - // Everything else is part of a number. - EXPECT_TRUE(IsDigits(rss_str.substr(0, rss_str.length() - 3))) << rss_str; - // ... which is not 0. - EXPECT_NE('0', rss_str[0]); - - const auto data_it = status.find("VmData"); - ASSERT_NE(data_it, status.end()); - - absl::string_view data_str(data_it->second); - - // Room for the " kB" suffix plus at least one digit. - ASSERT_GT(data_str.length(), 3); - EXPECT_TRUE(absl::EndsWith(data_str, " kB")); - // Everything else is part of a number. - EXPECT_TRUE(IsDigits(data_str.substr(0, data_str.length() - 3))) << data_str; - // ... which is not 0. - EXPECT_NE('0', data_str[0]); -} - -// Parse an array of NUL-terminated char* arrays, returning a vector of -// strings. -std::vector<std::string> ParseNulTerminatedStrings(std::string contents) { - EXPECT_EQ('\0', contents.back()); - // The split will leave an empty string if the NUL-byte remains, so pop - // it. - contents.pop_back(); - - return absl::StrSplit(contents, '\0'); -} - -TEST(ProcPidCmdline, MatchesArgv) { - std::vector<std::string> proc_cmdline = ParseNulTerminatedStrings( - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/cmdline"))); - EXPECT_THAT(saved_argv, ContainerEq(proc_cmdline)); -} - -TEST(ProcPidEnviron, MatchesEnviron) { - std::vector<std::string> proc_environ = ParseNulTerminatedStrings( - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/environ"))); - // Get the environment from the environ variable, which we will compare with - // /proc/self/environ. - std::vector<std::string> env; - for (char** v = environ; *v; v++) { - env.push_back(*v); - } - EXPECT_THAT(env, ContainerEq(proc_environ)); -} - -TEST(ProcPidCmdline, SubprocessForkSameCmdline) { - std::vector<std::string> proc_cmdline_parent; - std::vector<std::string> proc_cmdline; - proc_cmdline_parent = ParseNulTerminatedStrings( - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/cmdline"))); - auto res = WithSubprocess( - [&](int pid) -> PosixError { - ASSIGN_OR_RETURN_ERRNO( - auto raw_cmdline, - GetContents(absl::StrCat("/proc/", pid, "/cmdline"))); - proc_cmdline = ParseNulTerminatedStrings(raw_cmdline); - return NoError(); - }, - nullptr, nullptr); - ASSERT_NO_ERRNO(res); - - for (size_t i = 0; i < proc_cmdline_parent.size(); i++) { - EXPECT_EQ(proc_cmdline_parent[i], proc_cmdline[i]); - } -} - -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]; - - EXPECT_THAT(ReadlinkWhileRunning("exe", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadlinkWhileRunning("ns/net", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadlinkWhileRunning("ns/pid", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadlinkWhileRunning("ns/user", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST(ProcPidSymlink, SubprocessZombied) { - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - char buf[1]; - - int want = EACCES; - if (!IsRunningOnGvisor()) { - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - if (version.major > 4 || (version.major == 4 && version.minor > 3)) { - want = ENOENT; - } - } - - EXPECT_THAT(ReadlinkWhileZombied("exe", buf, sizeof(buf)), - SyscallFailsWithErrno(want)); - - if (!IsRunningOnGvisor()) { - EXPECT_THAT(ReadlinkWhileZombied("ns/net", buf, sizeof(buf)), - SyscallFailsWithErrno(want)); - } - - // FIXME(gvisor.dev/issue/164): Inconsistent behavior between linux on proc - // files. - // - // ~4.3: Syscall fails with EACCES. - // 4.17: Syscall succeeds and returns 1. - // - if (!IsRunningOnGvisor()) { - return; - } - - EXPECT_THAT(ReadlinkWhileZombied("ns/pid", buf, sizeof(buf)), - SyscallFailsWithErrno(want)); - - EXPECT_THAT(ReadlinkWhileZombied("ns/user", buf, sizeof(buf)), - SyscallFailsWithErrno(want)); -} - -// Test whether /proc/PID/ symlinks can be read for an exited process. -TEST(ProcPidSymlink, SubprocessExited) { - char buf[1]; - - EXPECT_THAT(ReadlinkWhileExited("exe", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - - EXPECT_THAT(ReadlinkWhileExited("ns/net", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - - EXPECT_THAT(ReadlinkWhileExited("ns/pid", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - - EXPECT_THAT(ReadlinkWhileExited("ns/user", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); -} - -// /proc/PID/exe points to the correct binary. -TEST(ProcPidExe, Subprocess) { - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/exe")); - auto expected_absolute_path = - ASSERT_NO_ERRNO_AND_VALUE(MakeAbsolute(link, "")); - - char actual[PATH_MAX + 1] = {}; - ASSERT_THAT(ReadlinkWhileRunning("exe", actual, sizeof(actual)), - SyscallSucceedsWithValue(Gt(0))); - EXPECT_EQ(actual, expected_absolute_path); -} - -// /proc/PID/cwd points to the correct directory. -TEST(ProcPidCwd, Subprocess) { - auto want = ASSERT_NO_ERRNO_AND_VALUE(GetCWD()); - - char got[PATH_MAX + 1] = {}; - ASSERT_THAT(ReadlinkWhileRunning("cwd", got, sizeof(got)), - SyscallSucceedsWithValue(Gt(0))); - EXPECT_EQ(got, want); -} - -// Test whether /proc/PID/ files can be read for a running process. -TEST(ProcPidFile, SubprocessRunning) { - char buf[1]; - - EXPECT_THAT(ReadWhileRunning("auxv", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("cmdline", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("comm", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("gid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("io", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("maps", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("stat", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("status", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("uid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("oom_score", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileRunning("oom_score_adj", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); -} - -// Test whether /proc/PID/ files can be read for a zombie process. -TEST(ProcPidFile, SubprocessZombie) { - char buf[1]; - - // FIXME(gvisor.dev/issue/164): Loosen requirement due to inconsistent - // behavior on different kernels. - // - // ~4.3: Succeds and returns 0. - // 4.17: Succeeds and returns 1. - // gVisor: Succeeds and returns 0. - EXPECT_THAT(ReadWhileZombied("auxv", buf, sizeof(buf)), SyscallSucceeds()); - - EXPECT_THAT(ReadWhileZombied("cmdline", buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(ReadWhileZombied("comm", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("gid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("maps", buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); - - EXPECT_THAT(ReadWhileZombied("stat", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("status", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("uid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("oom_score", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(ReadWhileZombied("oom_score_adj", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux - // on proc files. - // - // ~4.3: Fails and returns EACCES. - // gVisor & 4.17: Succeeds and returns 1. - // - // EXPECT_THAT(ReadWhileZombied("io", buf, sizeof(buf)), - // SyscallFailsWithErrno(EACCES)); -} - -// Test whether /proc/PID/ files can be read for an exited process. -TEST(ProcPidFile, SubprocessExited) { - char buf[1]; - - // FIXME(gvisor.dev/issue/164): Inconsistent behavior between kernels. - // - // ~4.3: Fails and returns ESRCH. - // gVisor: Fails with ESRCH. - // 4.17: Succeeds and returns 1. - // - // EXPECT_THAT(ReadWhileExited("auxv", buf, sizeof(buf)), - // SyscallFailsWithErrno(ESRCH)); - - EXPECT_THAT(ReadWhileExited("cmdline", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Succeeds on gVisor. - EXPECT_THAT(ReadWhileExited("comm", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - EXPECT_THAT(ReadWhileExited("gid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Succeeds on gVisor. - EXPECT_THAT(ReadWhileExited("io", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Returns EOF on gVisor. - EXPECT_THAT(ReadWhileExited("maps", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Succeeds on gVisor. - EXPECT_THAT(ReadWhileExited("stat", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Succeeds on gVisor. - EXPECT_THAT(ReadWhileExited("status", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - EXPECT_THAT(ReadWhileExited("uid_map", buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - if (!IsRunningOnGvisor()) { - // FIXME(gvisor.dev/issue/164): Succeeds on gVisor. - EXPECT_THAT(ReadWhileExited("oom_score", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); - } - - EXPECT_THAT(ReadWhileExited("oom_score_adj", buf, sizeof(buf)), - SyscallFailsWithErrno(ESRCH)); -} - -PosixError DirContains(absl::string_view path, - const std::vector<std::string>& expect, - const std::vector<std::string>& exclude) { - ASSIGN_OR_RETURN_ERRNO(auto listing, ListDir(path, false)); - - for (auto& expected_entry : expect) { - auto cursor = std::find(listing.begin(), listing.end(), expected_entry); - if (cursor == listing.end()) { - return PosixError( - ENOENT, - absl::StrCat("Failed to find one or more paths in '", path, "'")); - } - } - for (auto& excluded_entry : exclude) { - auto cursor = std::find(listing.begin(), listing.end(), excluded_entry); - if (cursor != listing.end()) { - return PosixError(ENOENT, absl::StrCat("File '", excluded_entry, - "' found in path '", path, "'")); - } - } - return NoError(); -} - -PosixError EventuallyDirContains(absl::string_view path, - const std::vector<std::string>& expect, - const std::vector<std::string>& exclude) { - constexpr int kRetryCount = 100; - const absl::Duration kRetryDelay = absl::Milliseconds(100); - - for (int i = 0; i < kRetryCount; ++i) { - auto res = DirContains(path, expect, exclude); - if (res.ok()) { - return res; - } else if (i < kRetryCount - 1) { - // Sleep if this isn't the final iteration. - absl::SleepFor(kRetryDelay); - } - } - return PosixError(ETIMEDOUT, - "Timed out while waiting for directory to contain files "); -} - -std::vector<std::string> TaskFiles(const std::vector<pid_t>& pids) { - return ApplyVec<std::string>([](const pid_t p) { return absl::StrCat(p); }, - pids); -} - -TEST(ProcTask, Basic) { - EXPECT_NO_ERRNO( - DirContains("/proc/self/task", {".", "..", absl::StrCat(getpid())}, {})); -} - -// Helper class for creating a new task in the current thread group. -class BlockingChild { - public: - BlockingChild() : thread_([=] { Start(); }) {} - ~BlockingChild() { Join(); } - - pid_t Tid() const { - absl::MutexLock ml(&mu_); - mu_.Await(absl::Condition(&tid_ready_)); - return tid_; - } - - void Join() { - { - absl::MutexLock ml(&mu_); - stop_ = true; - } - thread_.Join(); - } - - private: - void Start() { - absl::MutexLock ml(&mu_); - tid_ = syscall(__NR_gettid); - tid_ready_ = true; - mu_.Await(absl::Condition(&stop_)); - } - - mutable absl::Mutex mu_; - bool stop_ ABSL_GUARDED_BY(mu_) = false; - pid_t tid_; - bool tid_ready_ ABSL_GUARDED_BY(mu_) = false; - - // Must be last to ensure that the destructor for the thread is run before - // any other member of the object is destroyed. - ScopedThread thread_; -}; - -TEST(ProcTask, NewThreadAppears) { - BlockingChild child1; - EXPECT_NO_ERRNO( - DirContains("/proc/self/task", TaskFiles({child1.Tid()}), {})); -} - -TEST(ProcTask, KilledThreadsDisappear) { - BlockingChild child1; - EXPECT_NO_ERRNO( - DirContains("/proc/self/task", TaskFiles({child1.Tid()}), {})); - - // Stat child1's task file. Regression test for b/32097707. - struct stat statbuf; - const std::string child1_task_file = - absl::StrCat("/proc/self/task/", child1.Tid()); - EXPECT_THAT(stat(child1_task_file.c_str(), &statbuf), SyscallSucceeds()); - - BlockingChild child2; - EXPECT_NO_ERRNO(DirContains("/proc/self/task", - TaskFiles({child1.Tid(), child2.Tid()}), {})); - - BlockingChild child3; - BlockingChild child4; - BlockingChild child5; - EXPECT_NO_ERRNO( - DirContains("/proc/self/task", - TaskFiles({child1.Tid(), child2.Tid(), child3.Tid(), - child4.Tid(), child5.Tid()}), - {})); - - child2.Join(); - EXPECT_NO_ERRNO(EventuallyDirContains( - "/proc/self/task", - TaskFiles({child1.Tid(), child3.Tid(), child4.Tid(), child5.Tid()}), - TaskFiles({child2.Tid()}))); - - child1.Join(); - child4.Join(); - EXPECT_NO_ERRNO(EventuallyDirContains( - "/proc/self/task", TaskFiles({child3.Tid(), child5.Tid()}), - TaskFiles({child2.Tid(), child1.Tid(), child4.Tid()}))); - - // Stat child1's task file again. This time it should fail. See b/32097707. - EXPECT_THAT(stat(child1_task_file.c_str(), &statbuf), - SyscallFailsWithErrno(ENOENT)); - - child3.Join(); - child5.Join(); - EXPECT_NO_ERRNO( - EventuallyDirContains("/proc/self/task", {}, - TaskFiles({child2.Tid(), child1.Tid(), child4.Tid(), - child3.Tid(), child5.Tid()}))); -} - -TEST(ProcTask, ChildTaskDir) { - BlockingChild child1; - EXPECT_NO_ERRNO( - DirContains("/proc/self/task", TaskFiles({child1.Tid()}), {})); - EXPECT_NO_ERRNO(DirContains(absl::StrCat("/proc/", child1.Tid(), "/task"), - TaskFiles({child1.Tid()}), {})); -} - -PosixError VerifyPidDir(std::string path) { - return DirContains(path, {"exe", "fd", "io", "maps", "ns", "stat", "status"}, - {}); -} - -TEST(ProcTask, VerifyTaskDir) { - EXPECT_NO_ERRNO(VerifyPidDir("/proc/self")); - - EXPECT_NO_ERRNO(VerifyPidDir(absl::StrCat("/proc/self/task/", getpid()))); - BlockingChild child1; - EXPECT_NO_ERRNO(VerifyPidDir(absl::StrCat("/proc/self/task/", child1.Tid()))); - - // Only the first level of task directories should contain the 'task' - // directory. That is: - // - // /proc/1234/task <- should exist - // /proc/1234/task/1234/task <- should not exist - // /proc/1234/task/1235/task <- should not exist (where 1235 is in the same - // thread group as 1234). - EXPECT_NO_ERRNO( - DirContains(absl::StrCat("/proc/self/task/", getpid()), {}, {"task"})); -} - -TEST(ProcTask, TaskDirCannotBeDeleted) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - EXPECT_THAT(rmdir("/proc/self/task"), SyscallFails()); - EXPECT_THAT(rmdir(absl::StrCat("/proc/self/task/", getpid()).c_str()), - SyscallFailsWithErrno(EACCES)); -} - -TEST(ProcTask, TaskDirHasCorrectMetadata) { - struct stat st; - EXPECT_THAT(stat("/proc/self/task", &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - - // Verify file is readable and executable by everyone. - mode_t expected_permissions = - S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - mode_t permissions = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); - EXPECT_EQ(expected_permissions, permissions); -} - -TEST(ProcTask, TaskDirCanSeekToEnd) { - const FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/task", O_RDONLY)); - EXPECT_THAT(lseek(dirfd.get(), 0, SEEK_END), SyscallSucceeds()); -} - -TEST(ProcTask, VerifyTaskDirNlinks) { - const auto fn = [] { - // A task directory will have 3 links if the taskgroup has a single - // thread. For example, the following shows where the links to - // '/proc/12345/task' comes from for a single threaded process with pid - // 12345: - // - // /proc/12345/task <-- 1 link for the directory itself - // . <-- link from "." - // .. - // 12345 - // . - // .. <-- link from ".." to parent. - // <other contents of a task dir> - // - // We can't assert an absolute number of links since we don't control how - // many threads the test framework spawns. Instead, we'll ensure creating a - // new thread increases the number of links as expected. - - // Once we reach the test body, we can count on the thread count being - // stable unless we spawn a new one. - const uint64_t initial_links = - TEST_CHECK_NO_ERRNO_AND_VALUE(Links("/proc/self/task")); - TEST_CHECK(initial_links >= 3); - - // For each new subtask, we should gain a new link. - BlockingChild child1; - uint64_t links = TEST_CHECK_NO_ERRNO_AND_VALUE(Links("/proc/self/task")); - TEST_CHECK(links == initial_links + 1); - - BlockingChild child2; - links = TEST_CHECK_NO_ERRNO_AND_VALUE(Links("/proc/self/task")); - TEST_CHECK(links == initial_links + 2); - }; - // Run as a forked process to prevent terminating tasks from other tests to - // show up here and race with the count. - EXPECT_THAT(InForkedProcess(fn), IsPosixErrorOkAndHolds(0)); -} - -TEST(ProcTask, CommContainsThreadNameAndTrailingNewline) { - constexpr char kThreadName[] = "TestThread12345"; - ASSERT_THAT(prctl(PR_SET_NAME, kThreadName), SyscallSucceeds()); - - auto thread_name = ASSERT_NO_ERRNO_AND_VALUE( - GetContents(JoinPath("/proc", absl::StrCat(getpid()), "task", - absl::StrCat(syscall(SYS_gettid)), "comm"))); - EXPECT_EQ(absl::StrCat(kThreadName, "\n"), thread_name); -} - -TEST(ProcTaskNs, NsDirExistsAndHasCorrectMetadata) { - EXPECT_NO_ERRNO(DirContains("/proc/self/ns", {"net", "pid", "user"}, {})); - - // Let's just test the 'pid' entry, all of them are very similar. - struct stat st; - EXPECT_THAT(lstat("/proc/self/ns/pid", &st), SyscallSucceeds()); - EXPECT_TRUE(S_ISLNK(st.st_mode)); - - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/self/ns/pid")); - EXPECT_THAT(link, ::testing::StartsWith("pid:[")); -} - -TEST(ProcTaskNs, AccessOnNsNodeSucceeds) { - EXPECT_THAT(access("/proc/self/ns/pid", F_OK), SyscallSucceeds()); -} - -TEST(ProcSysKernelHostname, Exists) { - EXPECT_THAT(open("/proc/sys/kernel/hostname", O_RDONLY), SyscallSucceeds()); -} - -TEST(ProcSysKernelHostname, MatchesUname) { - struct utsname buf; - EXPECT_THAT(uname(&buf), SyscallSucceeds()); - const std::string hostname = absl::StrCat(buf.nodename, "\n"); - auto procfs_hostname = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/hostname")); - EXPECT_EQ(procfs_hostname, hostname); -} - -TEST(ProcSysVmMmapMinAddr, HasNumericValue) { - const std::string mmap_min_addr_str = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/mmap_min_addr")); - uintptr_t mmap_min_addr; - EXPECT_TRUE(absl::SimpleAtoi(mmap_min_addr_str, &mmap_min_addr)) - << "/proc/sys/vm/mmap_min_addr does not contain a numeric value: " - << mmap_min_addr_str; -} - -TEST(ProcSysVmOvercommitMemory, HasNumericValue) { - const std::string overcommit_memory_str = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/overcommit_memory")); - uintptr_t overcommit_memory; - EXPECT_TRUE(absl::SimpleAtoi(overcommit_memory_str, &overcommit_memory)) - << "/proc/sys/vm/overcommit_memory does not contain a numeric value: " - << overcommit_memory; -} - -// Check that link for proc fd entries point the target node, not the -// symlink itself. Regression test for b/31155070. -TEST(ProcTaskFd, FstatatFollowsSymlink) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - struct stat sproc = {}; - EXPECT_THAT( - fstatat(-1, absl::StrCat("/proc/self/fd/", fd.get()).c_str(), &sproc, 0), - SyscallSucceeds()); - - struct stat sfile = {}; - EXPECT_THAT(fstatat(-1, file.path().c_str(), &sfile, 0), SyscallSucceeds()); - - // If fstatat follows the fd symlink, the device and inode numbers should - // match at a minimum. - EXPECT_EQ(sproc.st_dev, sfile.st_dev); - EXPECT_EQ(sproc.st_ino, sfile.st_ino); - EXPECT_EQ(0, memcmp(&sfile, &sproc, sizeof(sfile))); -} - -TEST(ProcFilesystems, Bug65172365) { - std::string proc_filesystems = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/filesystems")); - ASSERT_FALSE(proc_filesystems.empty()); -} - -TEST(ProcFilesystems, PresenceOfShmMaxMniAll) { - uint64_t shmmax = 0; - uint64_t shmall = 0; - uint64_t shmmni = 0; - std::string proc_file; - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmax")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmax)); - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmall")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmall)); - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmni")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmni)); - - ASSERT_GT(shmmax, 0); - ASSERT_GT(shmall, 0); - ASSERT_GT(shmmni, 0); - ASSERT_LE(shmall, shmmax); - - // These values should never be higher than this by default, for more - // information see uapi/linux/shm.h - ASSERT_LE(shmmax, ULONG_MAX - (1UL << 24)); - ASSERT_LE(shmall, ULONG_MAX - (1UL << 24)); -} - -TEST(ProcFilesystems, PresenceOfSem) { - uint32_t semmsl = 0; - uint32_t semmns = 0; - uint32_t semopm = 0; - uint32_t semmni = 0; - std::string proc_file; - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem")); - ASSERT_FALSE(proc_file.empty()); - std::vector<absl::string_view> sem_limits = - absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace()); - ASSERT_EQ(sem_limits.size(), 4); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni)); - - ASSERT_EQ(semmsl, SEMMSL); - ASSERT_EQ(semmns, SEMMNS); - ASSERT_EQ(semopm, SEMOPM); - ASSERT_EQ(semmni, SEMMNI); -} - -// Check that /proc/mounts is a symlink to self/mounts. -TEST(ProcMounts, IsSymlink) { - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts")); - EXPECT_EQ(link, "self/mounts"); -} - -TEST(ProcSelfMountinfo, RequiredFieldsArePresent) { - auto mountinfo = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mountinfo")); - EXPECT_THAT( - mountinfo, - AllOf( - // Root mount. - ContainsRegex( - R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ /\S* / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"), - // Proc mount - always rw. - ContainsRegex( - R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)"))); -} - -// Check that /proc/self/mounts looks something like a real mounts file. -TEST(ProcSelfMounts, RequiredFieldsArePresent) { - auto mounts = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mounts")); - EXPECT_THAT(mounts, - AllOf( - // Root mount. - ContainsRegex(R"(\S+ / \S+ (rw|ro)\S* [0-9]+ [0-9]+\s)"), - // Root mount. - ContainsRegex(R"(\S+ /proc \S+ rw\S* [0-9]+ [0-9]+\s)"))); -} - -void CheckDuplicatesRecursively(std::string path) { - std::vector<std::string> child_dirs; - - // There is the known issue of the linux procfs, that two consequent calls of - // readdir can return the same entry twice if between these calls one or more - // entries have been removed from this directory. - int max_attempts = 5; - for (int i = 0; i < max_attempts; i++) { - child_dirs.clear(); - errno = 0; - bool success = true; - DIR* dir = opendir(path.c_str()); - if (dir == nullptr) { - // Ignore any directories we can't read or missing directories as the - // directory could have been deleted/mutated from the time the parent - // directory contents were read. - return; - } - auto dir_closer = Cleanup([&dir]() { closedir(dir); }); - absl::node_hash_set<std::string> children; - while (true) { - // Readdir(3): If the end of the directory stream is reached, NULL is - // returned and errno is not changed. If an error occurs, NULL is - // returned and errno is set appropriately. To distinguish end of stream - // and from an error, set errno to zero before calling readdir() and then - // check the value of errno if NULL is returned. - errno = 0; - struct dirent* dp = readdir(dir); - if (dp == nullptr) { - // Linux will return EINVAL when calling getdents on a /proc/tid/net - // file corresponding to a zombie task. - // See fs/proc/proc_net.c:proc_tgid_net_readdir(). - // - // We just ignore the directory in this case. - if (errno == EINVAL && absl::StartsWith(path, "/proc/") && - absl::EndsWith(path, "/net")) { - break; - } - // We may also see permission failures traversing some files. - if (errno == EACCES && absl::StartsWith(path, "/proc/")) { - break; - } - - // Otherwise, no errors are allowed. - ASSERT_EQ(errno, 0) << path; - break; // We're done. - } - - const std::string name = dp->d_name; - - if (name == "." || name == "..") { - continue; - } - - // Ignore a duplicate entry if it isn't the last attempt. - if (i == max_attempts - 1) { - ASSERT_EQ(children.find(name), children.end()) - << absl::StrCat(path, "/", name); - } else if (children.find(name) != children.end()) { - std::cerr << "Duplicate entry: " << i << ":" - << absl::StrCat(path, "/", name) << std::endl; - success = false; - break; - } - children.insert(name); - - if (dp->d_type == DT_DIR) { - child_dirs.push_back(name); - } - } - if (success) { - break; - } - } - for (auto dname = child_dirs.begin(); dname != child_dirs.end(); dname++) { - CheckDuplicatesRecursively(absl::StrCat(path, "/", *dname)); - } -} - -TEST(Proc, NoDuplicates) { CheckDuplicatesRecursively("/proc"); } - -// Most /proc/PID files are owned by the task user with SUID_DUMP_USER. -TEST(ProcPid, UserDumpableOwner) { - int before; - ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds()); - auto cleanup = Cleanup([before] { - ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds()); - }); - - EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_USER), SyscallSucceeds()); - - // This applies to the task directory itself and files inside. - struct stat st; - ASSERT_THAT(stat("/proc/self/", &st), SyscallSucceeds()); - EXPECT_EQ(st.st_uid, geteuid()); - EXPECT_EQ(st.st_gid, getegid()); - - ASSERT_THAT(stat("/proc/self/stat", &st), SyscallSucceeds()); - EXPECT_EQ(st.st_uid, geteuid()); - EXPECT_EQ(st.st_gid, getegid()); -} - -// /proc/PID files are owned by root with SUID_DUMP_DISABLE. -TEST(ProcPid, RootDumpableOwner) { - int before; - ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds()); - auto cleanup = Cleanup([before] { - ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds()); - }); - - EXPECT_THAT(prctl(PR_SET_DUMPABLE, SUID_DUMP_DISABLE), SyscallSucceeds()); - - // This *does not* applies to the task directory itself (or other 0555 - // directories), but does to files inside. - struct stat st; - ASSERT_THAT(stat("/proc/self/", &st), SyscallSucceeds()); - EXPECT_EQ(st.st_uid, geteuid()); - EXPECT_EQ(st.st_gid, getegid()); - - // This file is owned by root. Also allow nobody in case this test is running - // in a userns without root mapped. - ASSERT_THAT(stat("/proc/self/stat", &st), SyscallSucceeds()); - EXPECT_THAT(st.st_uid, AnyOf(Eq(0), Eq(65534))); - EXPECT_THAT(st.st_gid, AnyOf(Eq(0), Eq(65534))); -} - -TEST(Proc, GetdentsEnoent) { - FileDescriptor fd; - ASSERT_NO_ERRNO(WithSubprocess( - [&](int pid) -> PosixError { - // Running. - ASSIGN_OR_RETURN_ERRNO(fd, Open(absl::StrCat("/proc/", pid, "/task"), - O_RDONLY | O_DIRECTORY)); - - return NoError(); - }, - nullptr, nullptr)); - char buf[1024]; - ASSERT_THAT(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf)), - SyscallFailsWithErrno(ENOENT)); -} - -void CheckSyscwFromIOFile(const std::string& path, const std::string& regex) { - std::string output; - ASSERT_NO_ERRNO(GetContents(path, &output)); - ASSERT_THAT(output, ContainsRegex(absl::StrCat("syscw:\\s+", regex, "\n"))); -} - -// Checks that there is variable accounting of IO between threads/tasks. -TEST(Proc, PidTidIOAccounting) { - absl::Notification notification; - - // Run a thread with a bunch of writes. Check that io account records exactly - // the number of write calls. File open/close is there to prevent buffering. - ScopedThread writer([¬ification] { - const int num_writes = 100; - for (int i = 0; i < num_writes; i++) { - auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_NO_ERRNO(SetContents(path.path(), "a")); - } - notification.Notify(); - const std::string& writer_dir = - absl::StrCat("/proc/", getpid(), "/task/", gettid(), "/io"); - - CheckSyscwFromIOFile(writer_dir, std::to_string(num_writes)); - }); - - // Run a thread and do no writes. Check that no writes are recorded. - ScopedThread noop([¬ification] { - notification.WaitForNotification(); - const std::string& noop_dir = - absl::StrCat("/proc/", getpid(), "/task/", gettid(), "/io"); - - CheckSyscwFromIOFile(noop_dir, "0"); - }); - - writer.Join(); - noop.Join(); -} - -TEST(Proc, Statfs) { - struct statfs st; - EXPECT_THAT(statfs("/proc", &st), SyscallSucceeds()); - if (IsRunningWithVFS1()) { - EXPECT_EQ(st.f_type, ANON_INODE_FS_MAGIC); - } else { - EXPECT_EQ(st.f_type, PROC_SUPER_MAGIC); - } - EXPECT_EQ(st.f_bsize, getpagesize()); - EXPECT_EQ(st.f_namelen, NAME_MAX); -} - -} // namespace -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - for (int i = 0; i < argc; ++i) { - gvisor::testing::saved_argv.emplace_back(std::string(argv[i])); - } - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc deleted file mode 100644 index 20f1dc305..000000000 --- a/test/syscalls/linux/proc_net.cc +++ /dev/null @@ -1,604 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <errno.h> -#include <netinet/in.h> -#include <poll.h> -#include <sys/socket.h> -#include <sys/syscall.h> -#include <sys/types.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -constexpr const char kProcNet[] = "/proc/net"; -constexpr const char kIpForward[] = "/proc/sys/net/ipv4/ip_forward"; -constexpr const char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; - -TEST(ProcNetSymlinkTarget, FileMode) { - struct stat s; - ASSERT_THAT(stat(kProcNet, &s), SyscallSucceeds()); - EXPECT_EQ(s.st_mode & S_IFMT, S_IFDIR); - EXPECT_EQ(s.st_mode & 0777, 0555); -} - -TEST(ProcNetSymlink, FileMode) { - struct stat s; - ASSERT_THAT(lstat(kProcNet, &s), SyscallSucceeds()); - EXPECT_EQ(s.st_mode & S_IFMT, S_IFLNK); - EXPECT_EQ(s.st_mode & 0777, 0777); -} - -TEST(ProcNetSymlink, Contents) { - char buf[40] = {}; - int n = readlink(kProcNet, buf, sizeof(buf)); - ASSERT_THAT(n, SyscallSucceeds()); - - buf[n] = 0; - EXPECT_STREQ(buf, "self/net"); -} - -TEST(ProcNetIfInet6, Format) { - auto ifinet6 = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/if_inet6")); - EXPECT_THAT(ifinet6, - ::testing::MatchesRegex( - // Ex: "00000000000000000000000000000001 01 80 10 80 lo\n" - "^([a-f0-9]{32}( [a-f0-9]{2}){4} +[a-z][a-z0-9]*\n)+$")); -} - -TEST(ProcSysNetIpv4Sack, Exists) { - EXPECT_THAT(open("/proc/sys/net/ipv4/tcp_sack", O_RDONLY), SyscallSucceeds()); -} - -TEST(ProcSysNetIpv4Sack, CanReadAndWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - auto const fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/sys/net/ipv4/tcp_sack", O_RDWR)); - - char buf; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_TRUE(buf == '0' || buf == '1') << "unexpected tcp_sack: " << buf; - - char to_write = (buf == '1') ? '0' : '1'; - EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0), - SyscallSucceedsWithValue(sizeof(to_write))); - - buf = 0; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - EXPECT_EQ(buf, to_write); -} - -// DeviceEntry is an entry in /proc/net/dev -struct DeviceEntry { - std::string name; - uint64_t stats[16]; -}; - -PosixErrorOr<std::vector<DeviceEntry>> GetDeviceMetricsFromProc( - const std::string dev) { - std::vector<std::string> lines = absl::StrSplit(dev, '\n'); - std::vector<DeviceEntry> entries; - - // /proc/net/dev prints 2 lines of headers followed by a line of metrics for - // each network interface. - for (unsigned i = 2; i < lines.size(); i++) { - // Ignore empty lines. - if (lines[i].empty()) { - continue; - } - - std::vector<std::string> values = - absl::StrSplit(lines[i], ' ', absl::SkipWhitespace()); - - // Interface name + 16 values. - if (values.size() != 17) { - return PosixError(EINVAL, "invalid line: " + lines[i]); - } - - DeviceEntry entry; - entry.name = values[0]; - // Skip the interface name and read only the values. - for (unsigned j = 1; j < 17; j++) { - uint64_t num; - if (!absl::SimpleAtoi(values[j], &num)) { - return PosixError(EINVAL, "invalid value: " + values[j]); - } - entry.stats[j - 1] = num; - } - - entries.push_back(entry); - } - - return entries; -} - -// TEST(ProcNetDev, Format) tests that /proc/net/dev is parsable and -// contains at least one entry. -TEST(ProcNetDev, Format) { - auto dev = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/dev")); - auto entries = ASSERT_NO_ERRNO_AND_VALUE(GetDeviceMetricsFromProc(dev)); - - EXPECT_GT(entries.size(), 0); -} - -PosixErrorOr<uint64_t> GetSNMPMetricFromProc(const std::string snmp, - const std::string& type, - const std::string& item) { - std::vector<std::string> snmp_vec = absl::StrSplit(snmp, '\n'); - - // /proc/net/snmp prints a line of headers followed by a line of metrics. - // Only search the headers. - for (unsigned i = 0; i < snmp_vec.size(); i = i + 2) { - if (!absl::StartsWith(snmp_vec[i], type)) continue; - - std::vector<std::string> fields = - absl::StrSplit(snmp_vec[i], ' ', absl::SkipWhitespace()); - - EXPECT_TRUE((i + 1) < snmp_vec.size()); - std::vector<std::string> values = - absl::StrSplit(snmp_vec[i + 1], ' ', absl::SkipWhitespace()); - - EXPECT_TRUE(!fields.empty() && fields.size() == values.size()); - - // Metrics start at the first index. - for (unsigned j = 1; j < fields.size(); j++) { - if (fields[j] == item) { - uint64_t val; - if (!absl::SimpleAtoi(values[j], &val)) { - return PosixError(EINVAL, - absl::StrCat("field is not a number: ", values[j])); - } - - return val; - } - } - } - // We should never get here. - return PosixError( - EINVAL, absl::StrCat("failed to find ", type, "/", item, " in:", snmp)); -} - -TEST(ProcNetSnmp, TcpReset_NoRandomSave) { - // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. - DisableSave ds; - - uint64_t oldAttemptFails; - uint64_t oldActiveOpens; - uint64_t oldOutRsts; - auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - oldActiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens")); - oldOutRsts = - ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "OutRsts")); - oldAttemptFails = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails")); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0)); - - struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_port = htons(1234), - }; - - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &(sin.sin_addr)), 1); - ASSERT_THAT(connect(s.get(), (struct sockaddr*)&sin, sizeof(sin)), - SyscallFailsWithErrno(ECONNREFUSED)); - - uint64_t newAttemptFails; - uint64_t newActiveOpens; - uint64_t newOutRsts; - snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - newActiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens")); - newOutRsts = - ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "OutRsts")); - newAttemptFails = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails")); - - EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); - EXPECT_EQ(oldOutRsts, newOutRsts - 1); - EXPECT_EQ(oldAttemptFails, newAttemptFails - 1); -} - -TEST(ProcNetSnmp, TcpEstab_NoRandomSave) { - // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. - DisableSave ds; - - uint64_t oldEstabResets; - uint64_t oldActiveOpens; - uint64_t oldPassiveOpens; - uint64_t oldCurrEstab; - auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - oldActiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens")); - oldPassiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "PassiveOpens")); - oldCurrEstab = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab")); - oldEstabResets = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "EstabResets")); - - FileDescriptor s_listen = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0)); - struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_port = 0, - }; - - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &(sin.sin_addr)), 1); - ASSERT_THAT(bind(s_listen.get(), (struct sockaddr*)&sin, sizeof(sin)), - SyscallSucceeds()); - ASSERT_THAT(listen(s_listen.get(), 1), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = sizeof(sin); - ASSERT_THAT( - getsockname(s_listen.get(), reinterpret_cast<sockaddr*>(&sin), &addrlen), - SyscallSucceeds()); - - FileDescriptor s_connect = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0)); - ASSERT_THAT(connect(s_connect.get(), (struct sockaddr*)&sin, sizeof(sin)), - SyscallSucceeds()); - - auto s_accept = - ASSERT_NO_ERRNO_AND_VALUE(Accept(s_listen.get(), nullptr, nullptr)); - - uint64_t newEstabResets; - uint64_t newActiveOpens; - uint64_t newPassiveOpens; - uint64_t newCurrEstab; - snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - newActiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens")); - newPassiveOpens = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "PassiveOpens")); - newCurrEstab = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab")); - - EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); - EXPECT_EQ(oldPassiveOpens, newPassiveOpens - 1); - EXPECT_EQ(oldCurrEstab, newCurrEstab - 2); - - // Send 1 byte from client to server. - ASSERT_THAT(send(s_connect.get(), "a", 1, 0), SyscallSucceedsWithValue(1)); - - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - - // Wait until server-side fd sees the data on its side but don't read it. - struct pollfd poll_fd = {s_accept.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now close server-side fd without reading the data which leads to a RST - // packet sent to client side. - s_accept.reset(-1); - - // Wait until client-side fd sees RST packet. - struct pollfd poll_fd1 = {s_connect.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd1, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now close client-side fd. - s_connect.reset(-1); - - // Wait until the process of the netstack. - absl::SleepFor(absl::Seconds(1)); - - snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - newCurrEstab = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab")); - newEstabResets = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Tcp", "EstabResets")); - - EXPECT_EQ(oldCurrEstab, newCurrEstab); - EXPECT_EQ(oldEstabResets, newEstabResets - 2); -} - -TEST(ProcNetSnmp, UdpNoPorts_NoRandomSave) { - // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. - DisableSave ds; - - uint64_t oldOutDatagrams; - uint64_t oldNoPorts; - auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - oldOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams")); - oldNoPorts = - ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts")); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_port = htons(4444), - }; - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &(sin.sin_addr)), 1); - ASSERT_THAT(sendto(s.get(), "a", 1, 0, (struct sockaddr*)&sin, sizeof(sin)), - SyscallSucceedsWithValue(1)); - - uint64_t newOutDatagrams; - uint64_t newNoPorts; - snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - newOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams")); - newNoPorts = - ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts")); - - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldNoPorts, newNoPorts - 1); -} - -TEST(ProcNetSnmp, UdpIn_NoRandomSave) { - // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. - const DisableSave ds; - - uint64_t oldOutDatagrams; - uint64_t oldInDatagrams; - auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - oldOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams")); - oldInDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams")); - - std::cerr << "snmp: " << std::endl << snmp << std::endl; - FileDescriptor server = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_port = htons(0), - }; - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &(sin.sin_addr)), 1); - ASSERT_THAT(bind(server.get(), (struct sockaddr*)&sin, sizeof(sin)), - SyscallSucceeds()); - // Get the port bound by the server socket. - socklen_t addrlen = sizeof(sin); - ASSERT_THAT( - getsockname(server.get(), reinterpret_cast<sockaddr*>(&sin), &addrlen), - SyscallSucceeds()); - - FileDescriptor client = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - ASSERT_THAT( - sendto(client.get(), "a", 1, 0, (struct sockaddr*)&sin, sizeof(sin)), - SyscallSucceedsWithValue(1)); - - char buf[128]; - ASSERT_THAT(recvfrom(server.get(), buf, sizeof(buf), 0, NULL, NULL), - SyscallSucceedsWithValue(1)); - - uint64_t newOutDatagrams; - uint64_t newInDatagrams; - snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - std::cerr << "new snmp: " << std::endl << snmp << std::endl; - newOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams")); - newInDatagrams = ASSERT_NO_ERRNO_AND_VALUE( - GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams")); - - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldInDatagrams, newInDatagrams - 1); -} - -TEST(ProcNetSnmp, CheckNetStat) { - // TODO(b/155123175): SNMP and netstat don't work on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - std::string contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/netstat")); - - int name_count = 0; - int value_count = 0; - std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (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 (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; - int64_t val; - if (absl::SimpleAtoi(values[j], &val)) { - ++value_count; - } - } - } - } - EXPECT_EQ(name_count, 4); - EXPECT_EQ(value_count, 4); -} - -TEST(ProcNetSnmp, Stat) { - struct stat st = {}; - ASSERT_THAT(stat("/proc/net/snmp", &st), SyscallSucceeds()); -} - -TEST(ProcNetSnmp, CheckSnmp) { - // TODO(b/155123175): SNMP and netstat don't work on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - std::string contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp")); - - int name_count = 0; - int value_count = 0; - std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (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 (long unsigned int j = 0; j < names.size() && j < values.size(); ++j) { - if (names[j] == "RetransSegs") { - ++name_count; - int64_t val; - if (absl::SimpleAtoi(values[j], &val)) { - ++value_count; - } - } - } - } - EXPECT_EQ(name_count, 1); - EXPECT_EQ(value_count, 1); -} - -TEST(ProcSysNetIpv4Recovery, Exists) { - EXPECT_THAT(open("/proc/sys/net/ipv4/tcp_recovery", O_RDONLY), - SyscallSucceeds()); -} - -TEST(ProcSysNetIpv4Recovery, CanReadAndWrite) { - // TODO(b/162988252): Enable save/restore for this test after the bug is - // fixed. - DisableSave ds; - - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - auto const fd = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/tcp_recovery", O_RDWR)); - - char buf[10] = {'\0'}; - char to_write = '2'; - - // Check initial value is set to 1. - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(to_write) + 1)); - if (IsRunningOnGvisor()) { - // TODO(gvisor.dev/issue/5243): TCPRACKLossDetection = 1 should be turned on - // by default. - EXPECT_EQ(strcmp(buf, "0\n"), 0); - } else { - EXPECT_EQ(strcmp(buf, "1\n"), 0); - } - - // Set tcp_recovery to one of the allowed constants. - EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0), - SyscallSucceedsWithValue(sizeof(to_write))); - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(to_write) + 1)); - EXPECT_EQ(strcmp(buf, "2\n"), 0); - - // Set tcp_recovery to any random value. - char kMessage[] = "100"; - EXPECT_THAT(PwriteFd(fd.get(), kMessage, strlen(kMessage), 0), - SyscallSucceedsWithValue(strlen(kMessage))); - EXPECT_THAT(PreadFd(fd.get(), buf, sizeof(kMessage), 0), - SyscallSucceedsWithValue(sizeof(kMessage))); - EXPECT_EQ(strcmp(buf, "100\n"), 0); -} - -TEST(ProcSysNetIpv4IpForward, Exists) { - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDONLY)); -} - -TEST(ProcSysNetIpv4IpForward, DefaultValueEqZero) { - // Test is only valid in sandbox. Not hermetic in native tests - // running on a arbitrary machine. - SKIP_IF(!IsRunningOnGvisor()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDONLY)); - - char buf = 101; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_EQ(buf, '0') << "unexpected ip_forward: " << buf; -} - -TEST(ProcSysNetIpv4IpForward, CanReadAndWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kIpForward, O_RDWR)); - - char buf; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_TRUE(buf == '0' || buf == '1') << "unexpected ip_forward: " << buf; - - // constexpr char to_write = '1'; - char to_write = (buf == '1') ? '0' : '1'; - EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0), - SyscallSucceedsWithValue(sizeof(to_write))); - - buf = 0; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - EXPECT_EQ(buf, to_write); -} - -TEST(ProcSysNetPortRange, CanReadAndWrite) { - int min; - int max; - std::string rangefile = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kRangeFile)); - ASSERT_EQ(rangefile.back(), '\n'); - rangefile.pop_back(); - std::vector<std::string> range = - absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); - ASSERT_GT(range.size(), 1); - ASSERT_TRUE(absl::SimpleAtoi(range.front(), &min)); - ASSERT_TRUE(absl::SimpleAtoi(range.back(), &max)); - EXPECT_LE(min, max); - - // If the file isn't writable, there's nothing else to do here. - if (access(kRangeFile, W_OK)) { - return; - } - - constexpr int kSize = 77; - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); - max = min + kSize; - const std::string small_range = absl::StrFormat("%d %d", min, max); - ASSERT_THAT(write(fd.get(), small_range.c_str(), small_range.size()), - SyscallSucceedsWithValue(small_range.size())); - - rangefile = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kRangeFile)); - ASSERT_EQ(rangefile.back(), '\n'); - rangefile.pop_back(); - range = absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); - ASSERT_GT(range.size(), 1); - ASSERT_TRUE(absl::SimpleAtoi(range.front(), &min)); - ASSERT_TRUE(absl::SimpleAtoi(range.back(), &max)); - EXPECT_EQ(min + kSize, max); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_net_tcp.cc b/test/syscalls/linux/proc_net_tcp.cc deleted file mode 100644 index 5b6e3e3cd..000000000 --- a/test/syscalls/linux/proc_net_tcp.cc +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using absl::StrCat; -using absl::StrSplit; - -constexpr char kProcNetTCPHeader[] = - " sl local_address rem_address st tx_queue rx_queue tr tm->when " - "retrnsmt uid timeout inode " - " "; - -// TCPEntry represents a single entry from /proc/net/tcp. -struct TCPEntry { - uint32_t local_addr; - uint16_t local_port; - - uint32_t remote_addr; - uint16_t remote_port; - - uint64_t state; - uint64_t uid; - uint64_t inode; -}; - -// Finds the first entry in 'entries' for which 'predicate' returns true. -// Returns true on match, and sets 'match' to a copy of the matching entry. If -// 'match' is null, it's ignored. -bool FindBy(const std::vector<TCPEntry>& entries, TCPEntry* match, - std::function<bool(const TCPEntry&)> predicate) { - for (const TCPEntry& entry : entries) { - if (predicate(entry)) { - if (match != nullptr) { - *match = entry; - } - return true; - } - } - return false; -} - -bool FindByLocalAddr(const std::vector<TCPEntry>& entries, TCPEntry* match, - const struct sockaddr* addr) { - uint32_t host = IPFromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy(entries, match, [host, port](const TCPEntry& e) { - return (e.local_addr == host && e.local_port == port); - }); -} - -bool FindByRemoteAddr(const std::vector<TCPEntry>& entries, TCPEntry* match, - const struct sockaddr* addr) { - uint32_t host = IPFromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy(entries, match, [host, port](const TCPEntry& e) { - return (e.remote_addr == host && e.remote_port == port); - }); -} - -// Returns a parsed representation of /proc/net/tcp entries. -PosixErrorOr<std::vector<TCPEntry>> ProcNetTCPEntries() { - std::string content; - RETURN_IF_ERRNO(GetContents("/proc/net/tcp", &content)); - - bool found_header = false; - std::vector<TCPEntry> entries; - std::vector<std::string> lines = StrSplit(content, '\n'); - std::cerr << "<contents of /proc/net/tcp>" << std::endl; - for (const std::string& line : lines) { - std::cerr << line << std::endl; - - if (!found_header) { - EXPECT_EQ(line, kProcNetTCPHeader); - found_header = true; - continue; - } - if (line.empty()) { - continue; - } - - // Parse a single entry from /proc/net/tcp. - // - // Example entries: - // - // clang-format off - // - // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode - // 0: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 1968 1 0000000000000000 100 0 0 10 0 - // 1: 0100007F:7533 00000000:0000 0A 00000000:00000000 00:00000000 00000000 120 0 10684 1 0000000000000000 100 0 0 10 0 - // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - // - // clang-format on - - TCPEntry entry; - std::vector<std::string> fields = - StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty()); - - ASSIGN_OR_RETURN_ERRNO(entry.local_addr, AtoiBase(fields[1], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16)); - - ASSIGN_OR_RETURN_ERRNO(entry.remote_addr, AtoiBase(fields[3], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16)); - - ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11])); - ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13])); - - entries.push_back(entry); - } - std::cerr << "<end of /proc/net/tcp>" << std::endl; - - return entries; -} - -TEST(ProcNetTCP, Exists) { - const std::string content = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/tcp")); - const std::string header_line = StrCat(kProcNetTCPHeader, "\n"); - if (IsRunningOnGvisor()) { - // Should be just the header since we don't have any tcp sockets yet. - EXPECT_EQ(content, header_line); - } else { - // On a general linux machine, we could have abitrary sockets on the system, - // so just check the header. - EXPECT_THAT(content, ::testing::StartsWith(header_line)); - } -} - -TEST(ProcNetTCP, EntryUID) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create()); - std::vector<TCPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - TCPEntry e; - ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr())); - EXPECT_EQ(e.uid, geteuid()); - ASSERT_TRUE(FindByRemoteAddr(entries, &e, sockets->first_addr())); - EXPECT_EQ(e.uid, geteuid()); -} - -TEST(ProcNetTCP, BindAcceptConnect) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create()); - std::vector<TCPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - // We can only make assertions about the total number of entries if we control - // the entire "machine". - if (IsRunningOnGvisor()) { - EXPECT_EQ(entries.size(), 2); - } - - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr())); - EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->first_addr())); -} - -TEST(ProcNetTCP, InodeReasonable) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPAcceptBindSocketPair(0).Create()); - std::vector<TCPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - - TCPEntry accepted_entry; - ASSERT_TRUE(FindByLocalAddr(entries, &accepted_entry, sockets->first_addr())); - EXPECT_NE(accepted_entry.inode, 0); - - TCPEntry client_entry; - ASSERT_TRUE(FindByRemoteAddr(entries, &client_entry, sockets->first_addr())); - EXPECT_NE(client_entry.inode, 0); - EXPECT_NE(accepted_entry.inode, client_entry.inode); -} - -TEST(ProcNetTCP, State) { - std::unique_ptr<FileDescriptor> server = - ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPUnboundSocket(0).Create()); - - auto test_addr = V4Loopback(); - ASSERT_THAT( - bind(server->get(), reinterpret_cast<struct sockaddr*>(&test_addr.addr), - test_addr.addr_len), - SyscallSucceeds()); - - struct sockaddr addr; - socklen_t addrlen = sizeof(struct sockaddr); - ASSERT_THAT(getsockname(server->get(), &addr, &addrlen), SyscallSucceeds()); - ASSERT_EQ(addrlen, sizeof(struct sockaddr)); - - ASSERT_THAT(listen(server->get(), 10), SyscallSucceeds()); - std::vector<TCPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - TCPEntry listen_entry; - ASSERT_TRUE(FindByLocalAddr(entries, &listen_entry, &addr)); - EXPECT_EQ(listen_entry.state, TCP_LISTEN); - - std::unique_ptr<FileDescriptor> client = - ASSERT_NO_ERRNO_AND_VALUE(IPv4TCPUnboundSocket(0).Create()); - ASSERT_THAT(RetryEINTR(connect)(client->get(), &addr, addrlen), - SyscallSucceeds()); - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - ASSERT_TRUE(FindByLocalAddr(entries, &listen_entry, &addr)); - EXPECT_EQ(listen_entry.state, TCP_LISTEN); - TCPEntry client_entry; - ASSERT_TRUE(FindByRemoteAddr(entries, &client_entry, &addr)); - EXPECT_EQ(client_entry.state, TCP_ESTABLISHED); - - FileDescriptor accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(server->get(), nullptr, nullptr)); - - const uint32_t accepted_local_host = IPFromInetSockaddr(&addr); - const uint16_t accepted_local_port = PortFromInetSockaddr(&addr); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCPEntries()); - TCPEntry accepted_entry; - ASSERT_TRUE(FindBy(entries, &accepted_entry, - [client_entry, accepted_local_host, - accepted_local_port](const TCPEntry& e) { - return e.local_addr == accepted_local_host && - e.local_port == accepted_local_port && - e.remote_addr == client_entry.local_addr && - e.remote_port == client_entry.local_port; - })); - EXPECT_EQ(accepted_entry.state, TCP_ESTABLISHED); -} - -constexpr char kProcNetTCP6Header[] = - " sl local_address remote_address" - " st tx_queue rx_queue tr tm->when retrnsmt" - " uid timeout inode"; - -// TCP6Entry represents a single entry from /proc/net/tcp6. -struct TCP6Entry { - struct in6_addr local_addr; - uint16_t local_port; - - struct in6_addr remote_addr; - uint16_t remote_port; - - uint64_t state; - uint64_t uid; - uint64_t inode; -}; - -bool IPv6AddrEqual(const struct in6_addr* a1, const struct in6_addr* a2) { - return memcmp(a1, a2, sizeof(struct in6_addr)) == 0; -} - -// Finds the first entry in 'entries' for which 'predicate' returns true. -// Returns true on match, and sets 'match' to a copy of the matching entry. If -// 'match' is null, it's ignored. -bool FindBy6(const std::vector<TCP6Entry>& entries, TCP6Entry* match, - std::function<bool(const TCP6Entry&)> predicate) { - for (const TCP6Entry& entry : entries) { - if (predicate(entry)) { - if (match != nullptr) { - *match = entry; - } - return true; - } - } - return false; -} - -const struct in6_addr* IP6FromInetSockaddr(const struct sockaddr* addr) { - auto* addr6 = reinterpret_cast<const struct sockaddr_in6*>(addr); - return &addr6->sin6_addr; -} - -bool FindByLocalAddr6(const std::vector<TCP6Entry>& entries, TCP6Entry* match, - const struct sockaddr* addr) { - const struct in6_addr* local = IP6FromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy6(entries, match, [local, port](const TCP6Entry& e) { - return (IPv6AddrEqual(&e.local_addr, local) && e.local_port == port); - }); -} - -bool FindByRemoteAddr6(const std::vector<TCP6Entry>& entries, TCP6Entry* match, - const struct sockaddr* addr) { - const struct in6_addr* remote = IP6FromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy6(entries, match, [remote, port](const TCP6Entry& e) { - return (IPv6AddrEqual(&e.remote_addr, remote) && e.remote_port == port); - }); -} - -void ReadIPv6Address(std::string s, struct in6_addr* addr) { - uint32_t a0, a1, a2, a3; - const char* fmt = "%08X%08X%08X%08X"; - EXPECT_EQ(sscanf(s.c_str(), fmt, &a0, &a1, &a2, &a3), 4); - - uint8_t* b = addr->s6_addr; - *((uint32_t*)&b[0]) = a0; - *((uint32_t*)&b[4]) = a1; - *((uint32_t*)&b[8]) = a2; - *((uint32_t*)&b[12]) = a3; -} - -// Returns a parsed representation of /proc/net/tcp6 entries. -PosixErrorOr<std::vector<TCP6Entry>> ProcNetTCP6Entries() { - std::string content; - RETURN_IF_ERRNO(GetContents("/proc/net/tcp6", &content)); - - bool found_header = false; - std::vector<TCP6Entry> entries; - std::vector<std::string> lines = StrSplit(content, '\n'); - std::cerr << "<contents of /proc/net/tcp6>" << std::endl; - for (const std::string& line : lines) { - std::cerr << line << std::endl; - - if (!found_header) { - EXPECT_EQ(line, kProcNetTCP6Header); - found_header = true; - continue; - } - if (line.empty()) { - continue; - } - - // Parse a single entry from /proc/net/tcp6. - // - // Example entries: - // - // clang-format off - // - // sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode - // 0: 00000000000000000000000000000000:1F90 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 876340 1 ffff8803da9c9380 100 0 0 10 0 - // 1: 00000000000000000000000000000000:C350 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 876987 1 ffff8803ec408000 100 0 0 10 0 - // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - // - // clang-format on - - TCP6Entry entry; - std::vector<std::string> fields = - StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty()); - - ReadIPv6Address(fields[1], &entry.local_addr); - ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16)); - ReadIPv6Address(fields[3], &entry.remote_addr); - ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11])); - ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13])); - - entries.push_back(entry); - } - std::cerr << "<end of /proc/net/tcp6>" << std::endl; - - return entries; -} - -TEST(ProcNetTCP6, Exists) { - const std::string content = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/tcp6")); - const std::string header_line = StrCat(kProcNetTCP6Header, "\n"); - if (IsRunningOnGvisor()) { - // Should be just the header since we don't have any tcp sockets yet. - EXPECT_EQ(content, header_line); - } else { - // On a general linux machine, we could have abitrary sockets on the system, - // so just check the header. - EXPECT_THAT(content, ::testing::StartsWith(header_line)); - } -} - -TEST(ProcNetTCP6, EntryUID) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create()); - std::vector<TCP6Entry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - TCP6Entry e; - - ASSERT_TRUE(FindByLocalAddr6(entries, &e, sockets->first_addr())); - EXPECT_EQ(e.uid, geteuid()); - ASSERT_TRUE(FindByRemoteAddr6(entries, &e, sockets->first_addr())); - EXPECT_EQ(e.uid, geteuid()); -} - -TEST(ProcNetTCP6, BindAcceptConnect) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create()); - std::vector<TCP6Entry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - // We can only make assertions about the total number of entries if we control - // the entire "machine". - if (IsRunningOnGvisor()) { - EXPECT_EQ(entries.size(), 2); - } - - EXPECT_TRUE(FindByLocalAddr6(entries, nullptr, sockets->first_addr())); - EXPECT_TRUE(FindByRemoteAddr6(entries, nullptr, sockets->first_addr())); -} - -TEST(ProcNetTCP6, InodeReasonable) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create()); - std::vector<TCP6Entry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - - TCP6Entry accepted_entry; - - ASSERT_TRUE( - FindByLocalAddr6(entries, &accepted_entry, sockets->first_addr())); - EXPECT_NE(accepted_entry.inode, 0); - - TCP6Entry client_entry; - ASSERT_TRUE(FindByRemoteAddr6(entries, &client_entry, sockets->first_addr())); - EXPECT_NE(client_entry.inode, 0); - EXPECT_NE(accepted_entry.inode, client_entry.inode); -} - -TEST(ProcNetTCP6, State) { - std::unique_ptr<FileDescriptor> server = - ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPUnboundSocket(0).Create()); - - auto test_addr = V6Loopback(); - ASSERT_THAT( - bind(server->get(), reinterpret_cast<struct sockaddr*>(&test_addr.addr), - test_addr.addr_len), - SyscallSucceeds()); - - struct sockaddr_in6 addr6; - socklen_t addrlen = sizeof(struct sockaddr_in6); - auto* addr = reinterpret_cast<struct sockaddr*>(&addr6); - ASSERT_THAT(getsockname(server->get(), addr, &addrlen), SyscallSucceeds()); - ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6)); - - ASSERT_THAT(listen(server->get(), 10), SyscallSucceeds()); - std::vector<TCP6Entry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - TCP6Entry listen_entry; - - ASSERT_TRUE(FindByLocalAddr6(entries, &listen_entry, addr)); - EXPECT_EQ(listen_entry.state, TCP_LISTEN); - - std::unique_ptr<FileDescriptor> client = - ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPUnboundSocket(0).Create()); - ASSERT_THAT(RetryEINTR(connect)(client->get(), addr, addrlen), - SyscallSucceeds()); - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - ASSERT_TRUE(FindByLocalAddr6(entries, &listen_entry, addr)); - EXPECT_EQ(listen_entry.state, TCP_LISTEN); - TCP6Entry client_entry; - ASSERT_TRUE(FindByRemoteAddr6(entries, &client_entry, addr)); - EXPECT_EQ(client_entry.state, TCP_ESTABLISHED); - - FileDescriptor accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(server->get(), nullptr, nullptr)); - - const struct in6_addr* local = IP6FromInetSockaddr(addr); - const uint16_t accepted_local_port = PortFromInetSockaddr(addr); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries()); - TCP6Entry accepted_entry; - ASSERT_TRUE(FindBy6( - entries, &accepted_entry, - [client_entry, local, accepted_local_port](const TCP6Entry& e) { - return IPv6AddrEqual(&e.local_addr, local) && - e.local_port == accepted_local_port && - IPv6AddrEqual(&e.remote_addr, &client_entry.local_addr) && - e.remote_port == client_entry.local_port; - })); - EXPECT_EQ(accepted_entry.state, TCP_ESTABLISHED); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_net_udp.cc b/test/syscalls/linux/proc_net_udp.cc deleted file mode 100644 index 786b4b4af..000000000 --- a/test/syscalls/linux/proc_net_udp.cc +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using absl::StrCat; -using absl::StrFormat; -using absl::StrSplit; - -constexpr char kProcNetUDPHeader[] = - " sl local_address rem_address st tx_queue rx_queue tr tm->when " - "retrnsmt uid timeout inode ref pointer drops "; - -// UDPEntry represents a single entry from /proc/net/udp. -struct UDPEntry { - uint32_t local_addr; - uint16_t local_port; - - uint32_t remote_addr; - uint16_t remote_port; - - uint64_t state; - uint64_t uid; - uint64_t inode; -}; - -std::string DescribeFirstInetSocket(const SocketPair& sockets) { - const struct sockaddr* addr = sockets.first_addr(); - return StrFormat("First test socket: fd:%d %8X:%4X", sockets.first_fd(), - IPFromInetSockaddr(addr), PortFromInetSockaddr(addr)); -} - -std::string DescribeSecondInetSocket(const SocketPair& sockets) { - const struct sockaddr* addr = sockets.second_addr(); - return StrFormat("Second test socket fd:%d %8X:%4X", sockets.second_fd(), - IPFromInetSockaddr(addr), PortFromInetSockaddr(addr)); -} - -// Finds the first entry in 'entries' for which 'predicate' returns true. -// Returns true on match, and set 'match' to a copy of the matching entry. If -// 'match' is null, it's ignored. -bool FindBy(const std::vector<UDPEntry>& entries, UDPEntry* match, - std::function<bool(const UDPEntry&)> predicate) { - for (const UDPEntry& entry : entries) { - if (predicate(entry)) { - if (match != nullptr) { - *match = entry; - } - return true; - } - } - return false; -} - -bool FindByLocalAddr(const std::vector<UDPEntry>& entries, UDPEntry* match, - const struct sockaddr* addr) { - uint32_t host = IPFromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy(entries, match, [host, port](const UDPEntry& e) { - return (e.local_addr == host && e.local_port == port); - }); -} - -bool FindByRemoteAddr(const std::vector<UDPEntry>& entries, UDPEntry* match, - const struct sockaddr* addr) { - uint32_t host = IPFromInetSockaddr(addr); - uint16_t port = PortFromInetSockaddr(addr); - return FindBy(entries, match, [host, port](const UDPEntry& e) { - return (e.remote_addr == host && e.remote_port == port); - }); -} - -PosixErrorOr<uint64_t> InodeFromSocketFD(int fd) { - ASSIGN_OR_RETURN_ERRNO(struct stat s, Fstat(fd)); - if (!S_ISSOCK(s.st_mode)) { - return PosixError(EINVAL, StrFormat("FD %d is not a socket", fd)); - } - return s.st_ino; -} - -PosixErrorOr<bool> FindByFD(const std::vector<UDPEntry>& entries, - UDPEntry* match, int fd) { - ASSIGN_OR_RETURN_ERRNO(uint64_t inode, InodeFromSocketFD(fd)); - return FindBy(entries, match, - [inode](const UDPEntry& e) { return (e.inode == inode); }); -} - -// Returns a parsed representation of /proc/net/udp entries. -PosixErrorOr<std::vector<UDPEntry>> ProcNetUDPEntries() { - std::string content; - RETURN_IF_ERRNO(GetContents("/proc/net/udp", &content)); - - bool found_header = false; - std::vector<UDPEntry> entries; - std::vector<std::string> lines = StrSplit(content, '\n'); - std::cerr << "<contents of /proc/net/udp>" << std::endl; - for (const std::string& line : lines) { - std::cerr << line << std::endl; - - if (!found_header) { - EXPECT_EQ(line, kProcNetUDPHeader); - found_header = true; - continue; - } - if (line.empty()) { - continue; - } - - // Parse a single entry from /proc/net/udp. - // - // Example entries: - // - // clang-format off - // - // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops - // 3503: 0100007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 33317 2 0000000000000000 0 - // 3518: 00000000:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 40394 2 0000000000000000 0 - // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - // - // clang-format on - - UDPEntry entry; - std::vector<std::string> fields = - StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty()); - - ASSIGN_OR_RETURN_ERRNO(entry.local_addr, AtoiBase(fields[1], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16)); - - ASSIGN_OR_RETURN_ERRNO(entry.remote_addr, AtoiBase(fields[3], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16)); - - ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11])); - ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13])); - - // Linux shares internal data structures between TCP and UDP sockets. The - // proc entries for UDP sockets share some fields with TCP sockets, but - // these fields should always be zero as they're not meaningful for UDP - // sockets. - EXPECT_EQ(fields[8], "00") << StrFormat("sl:%s, tr", fields[0]); - EXPECT_EQ(fields[9], "00000000") << StrFormat("sl:%s, tm->when", fields[0]); - EXPECT_EQ(fields[10], "00000000") - << StrFormat("sl:%s, retrnsmt", fields[0]); - EXPECT_EQ(fields[12], "0") << StrFormat("sl:%s, timeout", fields[0]); - - entries.push_back(entry); - } - std::cerr << "<end of /proc/net/udp>" << std::endl; - - return entries; -} - -TEST(ProcNetUDP, Exists) { - const std::string content = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/udp")); - const std::string header_line = StrCat(kProcNetUDPHeader, "\n"); - EXPECT_THAT(content, ::testing::StartsWith(header_line)); -} - -TEST(ProcNetUDP, EntryUID) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create()); - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - UDPEntry e; - ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_EQ(e.uid, geteuid()); - ASSERT_TRUE(FindByRemoteAddr(entries, &e, sockets->first_addr())) - << DescribeSecondInetSocket(*sockets); - EXPECT_EQ(e.uid, geteuid()); -} - -TEST(ProcNetUDP, FindMutualEntries) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create()); - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->first_addr())) - << DescribeSecondInetSocket(*sockets); - - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr())) - << DescribeSecondInetSocket(*sockets); - EXPECT_TRUE(FindByRemoteAddr(entries, nullptr, sockets->second_addr())) - << DescribeFirstInetSocket(*sockets); -} - -TEST(ProcNetUDP, EntriesRemovedOnClose) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create()); - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr())) - << DescribeSecondInetSocket(*sockets); - - EXPECT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - // First socket's entry should be gone, but the second socket's entry should - // still exist. - EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_TRUE(FindByLocalAddr(entries, nullptr, sockets->second_addr())) - << DescribeSecondInetSocket(*sockets); - - EXPECT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - // Both entries should be gone. - EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_FALSE(FindByLocalAddr(entries, nullptr, sockets->second_addr())) - << DescribeSecondInetSocket(*sockets); -} - -PosixErrorOr<std::unique_ptr<FileDescriptor>> BoundUDPSocket() { - ASSIGN_OR_RETURN_ERRNO(std::unique_ptr<FileDescriptor> socket, - IPv4UDPUnboundSocket(0).Create()); - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = 0; - - int res = bind(socket->get(), reinterpret_cast<const struct sockaddr*>(&addr), - sizeof(addr)); - if (res) { - return PosixError(errno, "bind()"); - } - return socket; -} - -TEST(ProcNetUDP, BoundEntry) { - std::unique_ptr<FileDescriptor> socket = - ASSERT_NO_ERRNO_AND_VALUE(BoundUDPSocket()); - struct sockaddr addr; - socklen_t len = sizeof(addr); - ASSERT_THAT(getsockname(socket->get(), &addr, &len), SyscallSucceeds()); - uint16_t port = PortFromInetSockaddr(&addr); - - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - UDPEntry e; - ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(FindByFD(entries, &e, socket->get()))); - EXPECT_EQ(e.local_port, port); - EXPECT_EQ(e.remote_addr, 0); - EXPECT_EQ(e.remote_port, 0); -} - -TEST(ProcNetUDP, BoundSocketStateClosed) { - std::unique_ptr<FileDescriptor> socket = - ASSERT_NO_ERRNO_AND_VALUE(BoundUDPSocket()); - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - UDPEntry e; - ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(FindByFD(entries, &e, socket->get()))); - EXPECT_EQ(e.state, TCP_CLOSE); -} - -TEST(ProcNetUDP, ConnectedSocketStateEstablished) { - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(IPv4UDPBidirectionalBindSocketPair(0).Create()); - std::vector<UDPEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUDPEntries()); - - UDPEntry e; - ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->first_addr())) - << DescribeFirstInetSocket(*sockets); - EXPECT_EQ(e.state, TCP_ESTABLISHED); - - ASSERT_TRUE(FindByLocalAddr(entries, &e, sockets->second_addr())) - << DescribeSecondInetSocket(*sockets); - EXPECT_EQ(e.state, TCP_ESTABLISHED); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_net_unix.cc b/test/syscalls/linux/proc_net_unix.cc deleted file mode 100644 index d61d94309..000000000 --- a/test/syscalls/linux/proc_net_unix.cc +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using absl::StrCat; -using absl::StreamFormat; -using absl::StrFormat; - -constexpr char kProcNetUnixHeader[] = - "Num RefCount Protocol Flags Type St Inode Path"; - -// Possible values of the "st" field in a /proc/net/unix entry. Source: Linux -// kernel, include/uapi/linux/net.h. -enum { - SS_FREE = 0, // Not allocated - SS_UNCONNECTED, // Unconnected to any socket - SS_CONNECTING, // In process of connecting - SS_CONNECTED, // Connected to socket - SS_DISCONNECTING // In process of disconnecting -}; - -// UnixEntry represents a single entry from /proc/net/unix. -struct UnixEntry { - uintptr_t addr; - uint64_t refs; - uint64_t protocol; - uint64_t flags; - uint64_t type; - uint64_t state; - uint64_t inode; - std::string path; -}; - -// Abstract socket paths can have either trailing null bytes or '@'s as padding -// at the end, depending on the linux version. This function strips any such -// padding. -void StripAbstractPathPadding(std::string* s) { - const char pad_char = s->back(); - if (pad_char != '\0' && pad_char != '@') { - return; - } - - const auto last_pos = s->find_last_not_of(pad_char); - if (last_pos != std::string::npos) { - s->resize(last_pos + 1); - } -} - -// Precondition: addr must be a unix socket address (i.e. sockaddr_un) and -// addr->sun_path must be null-terminated. This is always the case if addr comes -// from Linux: -// -// Per man unix(7): -// -// "When the address of a pathname socket is returned (by [getsockname(2)]), its -// length is -// -// offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 -// -// and sun_path contains the null-terminated pathname." -std::string ExtractPath(const struct sockaddr* addr) { - const char* path = - reinterpret_cast<const struct sockaddr_un*>(addr)->sun_path; - // Note: sockaddr_un.sun_path is an embedded character array of length - // UNIX_PATH_MAX, so we can always safely dereference the first 2 bytes below. - // - // We also rely on the path being null-terminated. - if (path[0] == 0) { - std::string abstract_path = StrCat("@", &path[1]); - StripAbstractPathPadding(&abstract_path); - return abstract_path; - } - return std::string(path); -} - -// Returns a parsed representation of /proc/net/unix entries. -PosixErrorOr<std::vector<UnixEntry>> ProcNetUnixEntries() { - std::string content; - RETURN_IF_ERRNO(GetContents("/proc/net/unix", &content)); - - bool skipped_header = false; - std::vector<UnixEntry> entries; - std::vector<std::string> lines = absl::StrSplit(content, '\n'); - std::cerr << "<contents of /proc/net/unix>" << std::endl; - for (const std::string& line : lines) { - // Emit the proc entry to the test output to provide context for the test - // results. - std::cerr << line << std::endl; - - if (!skipped_header) { - EXPECT_EQ(line, kProcNetUnixHeader); - skipped_header = true; - continue; - } - if (line.empty()) { - continue; - } - - // Parse a single entry from /proc/net/unix. - // - // Sample file: - // - // clang-format off - // - // Num RefCount Protocol Flags Type St Inode Path" - // ffffa130e7041c00: 00000002 00000000 00010000 0001 01 1299413685 /tmp/control_server/13293772586877554487 - // ffffa14f547dc400: 00000002 00000000 00010000 0001 01 3793 @remote_coredump - // - // clang-format on - // - // Note that from the second entry, the inode number can be padded using - // spaces, so we need to handle it separately during parsing. See - // net/unix/af_unix.c:unix_seq_show() for how these entries are produced. In - // particular, only the inode field is padded with spaces. - UnixEntry entry; - - // Process the first 6 fields, up to but not including "Inode". - std::vector<std::string> fields = - absl::StrSplit(line, absl::MaxSplits(' ', 6)); - - if (fields.size() < 7) { - return PosixError(EINVAL, StrFormat("Invalid entry: '%s'\n", line)); - } - - // AtoiBase can't handle the ':' in the "Num" field, so strip it out. - std::vector<std::string> addr = absl::StrSplit(fields[0], ':'); - ASSIGN_OR_RETURN_ERRNO(entry.addr, AtoiBase(addr[0], 16)); - - ASSIGN_OR_RETURN_ERRNO(entry.refs, AtoiBase(fields[1], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.protocol, AtoiBase(fields[2], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.flags, AtoiBase(fields[3], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.type, AtoiBase(fields[4], 16)); - ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16)); - - absl::string_view rest = absl::StripAsciiWhitespace(fields[6]); - fields = absl::StrSplit(rest, absl::MaxSplits(' ', 1)); - if (fields.empty()) { - return PosixError( - EINVAL, StrFormat("Invalid entry, missing 'Inode': '%s'\n", line)); - } - ASSIGN_OR_RETURN_ERRNO(entry.inode, AtoiBase(fields[0], 10)); - - entry.path = ""; - if (fields.size() > 1) { - entry.path = fields[1]; - StripAbstractPathPadding(&entry.path); - } - - entries.push_back(entry); - } - std::cerr << "<end of /proc/net/unix>" << std::endl; - - return entries; -} - -// Finds the first entry in 'entries' for which 'predicate' returns true. -// 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 (long unsigned int i = 0; i < entries.size(); ++i) { - if (predicate(entries[i])) { - *match = entries[i]; - return true; - } - } - return false; -} - -bool FindByPath(std::vector<UnixEntry> entries, UnixEntry* match, - const std::string& path) { - return FindBy(entries, match, - [path](const UnixEntry& e) { return e.path == path; }); -} - -TEST(ProcNetUnix, Exists) { - const std::string content = - ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/unix")); - const std::string header_line = StrCat(kProcNetUnixHeader, "\n"); - if (IsRunningOnGvisor()) { - // Should be just the header since we don't have any unix domain sockets - // yet. - EXPECT_EQ(content, header_line); - } else { - // However, on a general linux machine, we could have abitrary sockets on - // the system, so just check the header. - EXPECT_THAT(content, ::testing::StartsWith(header_line)); - } -} - -TEST(ProcNetUnix, FilesystemBindAcceptConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - FilesystemBoundUnixDomainSocketPair(SOCK_STREAM).Create()); - - std::string path1 = ExtractPath(sockets->first_addr()); - std::string path2 = ExtractPath(sockets->second_addr()); - std::cerr << StreamFormat("Server socket address (path1): %s\n", path1); - std::cerr << StreamFormat("Client socket address (path2): %s\n", path2); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - if (IsRunningOnGvisor()) { - EXPECT_EQ(entries.size(), 2); - } - - // The server-side socket's path is listed in the socket entry... - UnixEntry s1; - EXPECT_TRUE(FindByPath(entries, &s1, path1)); - - // ... but the client-side socket's path is not. - UnixEntry s2; - EXPECT_FALSE(FindByPath(entries, &s2, path2)); -} - -TEST(ProcNetUnix, AbstractBindAcceptConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractBoundUnixDomainSocketPair(SOCK_STREAM).Create()); - - std::string path1 = ExtractPath(sockets->first_addr()); - std::string path2 = ExtractPath(sockets->second_addr()); - std::cerr << StreamFormat("Server socket address (path1): '%s'\n", path1); - std::cerr << StreamFormat("Client socket address (path2): '%s'\n", path2); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - if (IsRunningOnGvisor()) { - EXPECT_EQ(entries.size(), 2); - } - - // The server-side socket's path is listed in the socket entry... - UnixEntry s1; - EXPECT_TRUE(FindByPath(entries, &s1, path1)); - - // ... but the client-side socket's path is not. - UnixEntry s2; - EXPECT_FALSE(FindByPath(entries, &s2, path2)); -} - -TEST(ProcNetUnix, SocketPair) { - // Under gvisor, ensure a socketpair() syscall creates exactly 2 new - // entries. We have no way to verify this under Linux, as we have no control - // over socket creation on a general Linux machine. - SKIP_IF(!IsRunningOnGvisor()); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - ASSERT_EQ(entries.size(), 0); - - auto sockets = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_STREAM).Create()); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - EXPECT_EQ(entries.size(), 2); -} - -TEST(ProcNetUnix, StreamSocketStateUnconnectedOnBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - - const std::string address = ExtractPath(sockets->first_addr()); - UnixEntry bind_entry; - ASSERT_TRUE(FindByPath(entries, &bind_entry, address)); - EXPECT_EQ(bind_entry.state, SS_UNCONNECTED); -} - -TEST(ProcNetUnix, StreamSocketStateStateUnconnectedOnListen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - - const std::string address = ExtractPath(sockets->first_addr()); - UnixEntry bind_entry; - ASSERT_TRUE(FindByPath(entries, &bind_entry, address)); - EXPECT_EQ(bind_entry.state, SS_UNCONNECTED); - - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - UnixEntry listen_entry; - ASSERT_TRUE( - FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr()))); - EXPECT_EQ(listen_entry.state, SS_UNCONNECTED); - // The bind and listen entries should refer to the same socket. - EXPECT_EQ(listen_entry.inode, bind_entry.inode); -} - -TEST(ProcNetUnix, StreamSocketStateStateConnectedOnAccept) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create()); - const std::string address = ExtractPath(sockets->first_addr()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds()); - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - UnixEntry listen_entry; - ASSERT_TRUE( - FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr()))); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - int clientfd; - ASSERT_THAT(clientfd = accept(sockets->first_fd(), nullptr, nullptr), - SyscallSucceeds()); - auto cleanup = Cleanup( - [clientfd]() { ASSERT_THAT(close(clientfd), SyscallSucceeds()); }); - - // Find the entry for the accepted socket. UDS proc entries don't have a - // remote address, so we distinguish the accepted socket from the listen - // socket by checking for a different inode. - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - UnixEntry accept_entry; - ASSERT_TRUE(FindBy( - entries, &accept_entry, [address, listen_entry](const UnixEntry& e) { - return e.path == address && e.inode != listen_entry.inode; - })); - EXPECT_EQ(accept_entry.state, SS_CONNECTED); - // Listen entry should still be in SS_UNCONNECTED state. - ASSERT_TRUE(FindBy(entries, &listen_entry, - [&sockets, listen_entry](const UnixEntry& e) { - return e.path == ExtractPath(sockets->first_addr()) && - e.inode == listen_entry.inode; - })); - EXPECT_EQ(listen_entry.state, SS_UNCONNECTED); -} - -TEST(ProcNetUnix, DgramSocketStateDisconnectingOnBind) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create()); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - - // On gVisor, the only two UDS on the system are the ones we just created and - // we rely on this to locate the test socket entries in the remainder of the - // test. On a generic Linux system, we have no easy way to locate the - // corresponding entries, as they don't have an address yet. - if (IsRunningOnGvisor()) { - ASSERT_EQ(entries.size(), 2); - for (const auto& e : entries) { - ASSERT_EQ(e.state, SS_DISCONNECTING); - } - } - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - const std::string address = ExtractPath(sockets->first_addr()); - UnixEntry bind_entry; - ASSERT_TRUE(FindByPath(entries, &bind_entry, address)); - EXPECT_EQ(bind_entry.state, SS_UNCONNECTED); -} - -TEST(ProcNetUnix, DgramSocketStateConnectingOnConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE( - AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create()); - - std::vector<UnixEntry> entries = - ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - - // On gVisor, the only two UDS on the system are the ones we just created and - // we rely on this to locate the test socket entries in the remainder of the - // test. On a generic Linux system, we have no easy way to locate the - // corresponding entries, as they don't have an address yet. - if (IsRunningOnGvisor()) { - ASSERT_EQ(entries.size(), 2); - for (const auto& e : entries) { - ASSERT_EQ(e.state, SS_DISCONNECTING); - } - } - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - const std::string address = ExtractPath(sockets->first_addr()); - UnixEntry bind_entry; - ASSERT_TRUE(FindByPath(entries, &bind_entry, address)); - - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries()); - - // Once again, we have no easy way to identify the connecting socket as it has - // no listed address. We can only identify the entry as the "non-bind socket - // entry" on gVisor, where we're guaranteed to have only the two entries we - // create during this test. - if (IsRunningOnGvisor()) { - ASSERT_EQ(entries.size(), 2); - UnixEntry connect_entry; - ASSERT_TRUE( - FindBy(entries, &connect_entry, [bind_entry](const UnixEntry& e) { - return e.inode != bind_entry.inode; - })); - EXPECT_EQ(connect_entry.state, SS_CONNECTING); - } -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_pid_oomscore.cc b/test/syscalls/linux/proc_pid_oomscore.cc deleted file mode 100644 index 707821a3f..000000000 --- a/test/syscalls/linux/proc_pid_oomscore.cc +++ /dev/null @@ -1,72 +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. - -#include <errno.h> - -#include <exception> -#include <iostream> -#include <string> - -#include "test/util/fs_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<int> ReadProcNumber(std::string path) { - ASSIGN_OR_RETURN_ERRNO(std::string contents, GetContents(path)); - EXPECT_EQ(contents[contents.length() - 1], '\n'); - - int num; - if (!absl::SimpleAtoi(contents, &num)) { - return PosixError(EINVAL, "invalid value: " + contents); - } - - return num; -} - -TEST(ProcPidOomscoreTest, BasicRead) { - auto const oom_score = - ASSERT_NO_ERRNO_AND_VALUE(ReadProcNumber("/proc/self/oom_score")); - EXPECT_LE(oom_score, 1000); - EXPECT_GE(oom_score, -1000); -} - -TEST(ProcPidOomscoreAdjTest, BasicRead) { - auto const oom_score = - ASSERT_NO_ERRNO_AND_VALUE(ReadProcNumber("/proc/self/oom_score_adj")); - - // oom_score_adj defaults to 0. - EXPECT_EQ(oom_score, 0); -} - -TEST(ProcPidOomscoreAdjTest, BasicWrite) { - constexpr int test_value = 7; - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/oom_score_adj", O_WRONLY)); - ASSERT_THAT( - RetryEINTR(write)(fd.get(), std::to_string(test_value).c_str(), 1), - SyscallSucceeds()); - - auto const oom_score = - ASSERT_NO_ERRNO_AND_VALUE(ReadProcNumber("/proc/self/oom_score_adj")); - EXPECT_EQ(oom_score, test_value); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_pid_smaps.cc b/test/syscalls/linux/proc_pid_smaps.cc deleted file mode 100644 index 738923822..000000000 --- a/test/syscalls/linux/proc_pid_smaps.cc +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stddef.h> -#include <stdint.h> - -#include <algorithm> -#include <iostream> -#include <string> -#include <utility> -#include <vector> - -#include "absl/container/flat_hash_set.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -using ::testing::Contains; -using ::testing::ElementsAreArray; -using ::testing::IsSupersetOf; -using ::testing::Not; -using ::testing::Optional; - -namespace gvisor { -namespace testing { - -namespace { - -struct ProcPidSmapsEntry { - ProcMapsEntry maps_entry; - - // These fields should always exist, as they were included in e070ad49f311 - // "[PATCH] add /proc/pid/smaps". - size_t size_kb; - size_t rss_kb; - size_t shared_clean_kb; - size_t shared_dirty_kb; - size_t private_clean_kb; - size_t private_dirty_kb; - - // These fields were added later and may not be present. - absl::optional<size_t> pss_kb; - absl::optional<size_t> referenced_kb; - absl::optional<size_t> anonymous_kb; - absl::optional<size_t> anon_huge_pages_kb; - absl::optional<size_t> shared_hugetlb_kb; - absl::optional<size_t> private_hugetlb_kb; - absl::optional<size_t> swap_kb; - absl::optional<size_t> swap_pss_kb; - absl::optional<size_t> kernel_page_size_kb; - absl::optional<size_t> mmu_page_size_kb; - absl::optional<size_t> locked_kb; - - // Caution: "Note that there is no guarantee that every flag and associated - // mnemonic will be present in all further kernel releases. Things get - // changed, the flags may be vanished or the reverse -- new added." - Linux - // Documentation/filesystems/proc.txt, on VmFlags. Avoid checking for any - // flags that are not extremely well-established. - absl::optional<std::vector<std::string>> vm_flags; -}; - -// Given the value part of a /proc/[pid]/smaps field containing a value in kB -// (for example, " 4 kB", returns the value in kB (in this example, 4). -PosixErrorOr<size_t> SmapsValueKb(absl::string_view value) { - // TODO(jamieliu): let us use RE2 or <regex> - std::pair<absl::string_view, absl::string_view> parts = - absl::StrSplit(value, ' ', absl::SkipEmpty()); - if (parts.second != "kB") { - return PosixError(EINVAL, - absl::StrCat("invalid smaps field value: ", value)); - } - ASSIGN_OR_RETURN_ERRNO(auto val_kb, Atoi<size_t>(parts.first)); - return val_kb; -} - -PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps( - absl::string_view contents) { - std::vector<ProcPidSmapsEntry> entries; - absl::optional<ProcPidSmapsEntry> entry; - bool have_size_kb = false; - bool have_rss_kb = false; - bool have_shared_clean_kb = false; - bool have_shared_dirty_kb = false; - bool have_private_clean_kb = false; - bool have_private_dirty_kb = false; - - auto const finish_entry = [&] { - if (entry) { - if (!have_size_kb) { - return PosixError(EINVAL, "smaps entry is missing Size"); - } - if (!have_rss_kb) { - return PosixError(EINVAL, "smaps entry is missing Rss"); - } - if (!have_shared_clean_kb) { - return PosixError(EINVAL, "smaps entry is missing Shared_Clean"); - } - if (!have_shared_dirty_kb) { - return PosixError(EINVAL, "smaps entry is missing Shared_Dirty"); - } - if (!have_private_clean_kb) { - return PosixError(EINVAL, "smaps entry is missing Private_Clean"); - } - if (!have_private_dirty_kb) { - return PosixError(EINVAL, "smaps entry is missing Private_Dirty"); - } - // std::move(entry.value()) instead of std::move(entry).value(), because - // otherwise tools may report a "use-after-move" warning, which is - // spurious because entry.emplace() below resets entry to a new - // ProcPidSmapsEntry. - entries.emplace_back(std::move(entry.value())); - } - entry.emplace(); - have_size_kb = false; - have_rss_kb = false; - have_shared_clean_kb = false; - have_shared_dirty_kb = false; - have_private_clean_kb = false; - have_private_dirty_kb = false; - return NoError(); - }; - - // Holds key/value pairs from smaps field lines. Declared here so it can be - // captured by reference by the following lambdas. - std::vector<absl::string_view> key_value; - - auto const on_required_field_kb = [&](size_t* field, bool* have_field) { - if (*have_field) { - return PosixError( - EINVAL, - absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); - } - ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); - *have_field = true; - return NoError(); - }; - - auto const on_optional_field_kb = [&](absl::optional<size_t>* field) { - if (*field) { - return PosixError( - EINVAL, - absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); - } - ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); - return NoError(); - }; - - absl::flat_hash_set<std::string> unknown_fields; - auto const on_unknown_field = [&] { - absl::string_view key = key_value[0]; - // Don't mention unknown fields more than once. - if (unknown_fields.count(key)) { - return; - } - unknown_fields.insert(std::string(key)); - std::cerr << "skipping unknown smaps field " << key << std::endl; - }; - - auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); - for (absl::string_view l : lines) { - // Is this line a valid /proc/[pid]/maps entry? - auto maybe_maps_entry = ParseProcMapsLine(l); - if (maybe_maps_entry.ok()) { - // This marks the beginning of a new /proc/[pid]/smaps entry. - RETURN_IF_ERRNO(finish_entry()); - entry->maps_entry = std::move(maybe_maps_entry).ValueOrDie(); - continue; - } - // Otherwise it's a field in an existing /proc/[pid]/smaps entry of the form - // "key:value" (where value in practice will be preceded by a variable - // amount of whitespace). - if (!entry) { - std::cerr << "smaps line not considered a maps line: " - << maybe_maps_entry.error().message() << std::endl; - return PosixError( - EINVAL, - absl::StrCat("smaps field line without preceding maps line: ", l)); - } - key_value = absl::StrSplit(l, absl::MaxSplits(':', 1)); - if (key_value.size() != 2) { - return PosixError(EINVAL, absl::StrCat("invalid smaps field line: ", l)); - } - absl::string_view const key = key_value[0]; - if (key == "Size") { - RETURN_IF_ERRNO(on_required_field_kb(&entry->size_kb, &have_size_kb)); - } else if (key == "Rss") { - RETURN_IF_ERRNO(on_required_field_kb(&entry->rss_kb, &have_rss_kb)); - } else if (key == "Shared_Clean") { - RETURN_IF_ERRNO( - on_required_field_kb(&entry->shared_clean_kb, &have_shared_clean_kb)); - } else if (key == "Shared_Dirty") { - RETURN_IF_ERRNO( - on_required_field_kb(&entry->shared_dirty_kb, &have_shared_dirty_kb)); - } else if (key == "Private_Clean") { - RETURN_IF_ERRNO(on_required_field_kb(&entry->private_clean_kb, - &have_private_clean_kb)); - } else if (key == "Private_Dirty") { - RETURN_IF_ERRNO(on_required_field_kb(&entry->private_dirty_kb, - &have_private_dirty_kb)); - } else if (key == "Pss") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->pss_kb)); - } else if (key == "Referenced") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->referenced_kb)); - } else if (key == "Anonymous") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->anonymous_kb)); - } else if (key == "AnonHugePages") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->anon_huge_pages_kb)); - } else if (key == "Shared_Hugetlb") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->shared_hugetlb_kb)); - } else if (key == "Private_Hugetlb") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->private_hugetlb_kb)); - } else if (key == "Swap") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_kb)); - } else if (key == "SwapPss") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_pss_kb)); - } else if (key == "KernelPageSize") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->kernel_page_size_kb)); - } else if (key == "MMUPageSize") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->mmu_page_size_kb)); - } else if (key == "Locked") { - RETURN_IF_ERRNO(on_optional_field_kb(&entry->locked_kb)); - } else if (key == "VmFlags") { - if (entry->vm_flags) { - return PosixError(EINVAL, "duplicate VmFlags line"); - } - entry->vm_flags = absl::StrSplit(key_value[1], ' ', absl::SkipEmpty()); - } else { - on_unknown_field(); - } - } - RETURN_IF_ERRNO(finish_entry()); - return entries; -}; - -TEST(ParseProcPidSmapsTest, Correctness) { - auto entries = ASSERT_NO_ERRNO_AND_VALUE( - ParseProcPidSmaps("0-10000 rw-s 00000000 00:00 0 " - " /dev/zero (deleted)\n" - "Size: 0 kB\n" - "Rss: 1 kB\n" - "Pss: 2 kB\n" - "Shared_Clean: 3 kB\n" - "Shared_Dirty: 4 kB\n" - "Private_Clean: 5 kB\n" - "Private_Dirty: 6 kB\n" - "Referenced: 7 kB\n" - "Anonymous: 8 kB\n" - "AnonHugePages: 9 kB\n" - "Shared_Hugetlb: 10 kB\n" - "Private_Hugetlb: 11 kB\n" - "Swap: 12 kB\n" - "SwapPss: 13 kB\n" - "KernelPageSize: 14 kB\n" - "MMUPageSize: 15 kB\n" - "Locked: 16 kB\n" - "FutureUnknownKey: 17 kB\n" - "VmFlags: rd wr sh mr mw me ms lo ?? sd \n")); - ASSERT_EQ(entries.size(), 1); - auto& entry = entries[0]; - EXPECT_EQ(entry.maps_entry.filename, "/dev/zero (deleted)"); - EXPECT_EQ(entry.size_kb, 0); - EXPECT_EQ(entry.rss_kb, 1); - EXPECT_THAT(entry.pss_kb, Optional(2)); - EXPECT_EQ(entry.shared_clean_kb, 3); - EXPECT_EQ(entry.shared_dirty_kb, 4); - EXPECT_EQ(entry.private_clean_kb, 5); - EXPECT_EQ(entry.private_dirty_kb, 6); - EXPECT_THAT(entry.referenced_kb, Optional(7)); - EXPECT_THAT(entry.anonymous_kb, Optional(8)); - EXPECT_THAT(entry.anon_huge_pages_kb, Optional(9)); - EXPECT_THAT(entry.shared_hugetlb_kb, Optional(10)); - EXPECT_THAT(entry.private_hugetlb_kb, Optional(11)); - EXPECT_THAT(entry.swap_kb, Optional(12)); - EXPECT_THAT(entry.swap_pss_kb, Optional(13)); - EXPECT_THAT(entry.kernel_page_size_kb, Optional(14)); - EXPECT_THAT(entry.mmu_page_size_kb, Optional(15)); - EXPECT_THAT(entry.locked_kb, Optional(16)); - EXPECT_THAT(entry.vm_flags, - Optional(ElementsAreArray({"rd", "wr", "sh", "mr", "mw", "me", - "ms", "lo", "??", "sd"}))); -} - -// Returns the unique entry in entries containing the given address. -PosixErrorOr<ProcPidSmapsEntry> FindUniqueSmapsEntry( - std::vector<ProcPidSmapsEntry> const& entries, uintptr_t addr) { - auto const pred = [&](ProcPidSmapsEntry const& entry) { - return entry.maps_entry.start <= addr && addr < entry.maps_entry.end; - }; - auto const it = std::find_if(entries.begin(), entries.end(), pred); - if (it == entries.end()) { - return PosixError(EINVAL, - absl::StrFormat("no entry contains address %#x", addr)); - } - auto const it2 = std::find_if(it + 1, entries.end(), pred); - if (it2 != entries.end()) { - return PosixError( - EINVAL, - absl::StrFormat("overlapping entries [%#x-%#x) and [%#x-%#x) both " - "contain address %#x", - it->maps_entry.start, it->maps_entry.end, - it2->maps_entry.start, it2->maps_entry.end, addr)); - } - return *it; -} - -PosixErrorOr<std::vector<ProcPidSmapsEntry>> ReadProcSelfSmaps() { - ASSIGN_OR_RETURN_ERRNO(std::string contents, GetContents("/proc/self/smaps")); - return ParseProcPidSmaps(contents); -} - -TEST(ProcPidSmapsTest, SharedAnon) { - // Map with MAP_POPULATE so we get some RSS. - Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon( - 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE)); - auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); - auto const entry = - ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); - - EXPECT_EQ(entry.size_kb, m.len() / 1024); - // It's possible that populated pages have been swapped out, so RSS might be - // less than size. - EXPECT_LE(entry.rss_kb, entry.size_kb); - - if (entry.pss_kb) { - // PSS should be exactly equal to RSS since no other address spaces should - // be sharing our new mapping. - EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); - } - - // "Shared" and "private" in smaps refers to whether or not *physical pages* - // are shared; thus all pages in our MAP_SHARED mapping should nevertheless - // be private. - EXPECT_EQ(entry.shared_clean_kb, 0); - EXPECT_EQ(entry.shared_dirty_kb, 0); - EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) - << "Private_Clean = " << entry.private_clean_kb - << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; - - // Shared anonymous mappings are implemented as a shmem file, so their pages - // are not PageAnon. - if (entry.anonymous_kb) { - EXPECT_EQ(entry.anonymous_kb.value(), 0); - } - - if (entry.vm_flags) { - EXPECT_THAT(entry.vm_flags.value(), - IsSupersetOf({"rd", "wr", "sh", "mr", "mw", "me", "ms"})); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); - } -} - -TEST(ProcPidSmapsTest, PrivateAnon) { - // Map with MAP_POPULATE so we get some RSS. - Mapping const m = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(2 * kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_POPULATE)); - auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); - auto const entry = - ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); - - // It's possible that our mapping was merged with another vma, so the smaps - // entry might be bigger than our original mapping. - EXPECT_GE(entry.size_kb, m.len() / 1024); - EXPECT_LE(entry.rss_kb, entry.size_kb); - if (entry.pss_kb) { - EXPECT_LE(entry.pss_kb.value(), entry.rss_kb); - } - - if (entry.anonymous_kb) { - EXPECT_EQ(entry.anonymous_kb.value(), entry.rss_kb); - } - - if (entry.vm_flags) { - EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"wr", "mr", "mw", "me"})); - // We passed PROT_WRITE to mmap. On at least x86, the mapping is in - // practice readable because there is no way to configure the MMU to make - // pages writable but not readable. However, VmFlags should reflect the - // flags set on the VMA, so "rd" (VM_READ) should not appear in VmFlags. - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("rd"))); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ms"))); - } -} - -TEST(ProcPidSmapsTest, SharedReadOnlyFile) { - size_t const kFileSize = kPageSize; - - auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_THAT(truncate(temp_file.path().c_str(), kFileSize), SyscallSucceeds()); - auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); - - auto const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kFileSize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd.get(), 0)); - auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); - auto const entry = - ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); - - // Most of the same logic as the SharedAnon case applies. - EXPECT_EQ(entry.size_kb, kFileSize / 1024); - EXPECT_LE(entry.rss_kb, entry.size_kb); - if (entry.pss_kb) { - EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); - } - EXPECT_EQ(entry.shared_clean_kb, 0); - EXPECT_EQ(entry.shared_dirty_kb, 0); - EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) - << "Private_Clean = " << entry.private_clean_kb - << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; - if (entry.anonymous_kb) { - EXPECT_EQ(entry.anonymous_kb.value(), 0); - } - - if (entry.vm_flags) { - EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"rd", "mr", "me", "ms"})); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("wr"))); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); - // Because the mapped file was opened O_RDONLY, the VMA is !VM_MAYWRITE and - // also !VM_SHARED. - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); - EXPECT_THAT(entry.vm_flags.value(), Not(Contains("mw"))); - } -} - -// Tests that gVisor's /proc/[pid]/smaps provides all of the fields we expect it -// to, which as of this writing is all fields provided by Linux 4.4. -TEST(ProcPidSmapsTest, GvisorFields) { - SKIP_IF(!IsRunningOnGvisor()); - auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); - for (auto const& entry : entries) { - EXPECT_TRUE(entry.pss_kb); - EXPECT_TRUE(entry.referenced_kb); - EXPECT_TRUE(entry.anonymous_kb); - EXPECT_TRUE(entry.anon_huge_pages_kb); - EXPECT_TRUE(entry.shared_hugetlb_kb); - EXPECT_TRUE(entry.private_hugetlb_kb); - EXPECT_TRUE(entry.swap_kb); - EXPECT_TRUE(entry.swap_pss_kb); - EXPECT_THAT(entry.kernel_page_size_kb, Optional(kPageSize / 1024)); - EXPECT_THAT(entry.mmu_page_size_kb, Optional(kPageSize / 1024)); - EXPECT_TRUE(entry.locked_kb); - EXPECT_TRUE(entry.vm_flags); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/proc_pid_uid_gid_map.cc b/test/syscalls/linux/proc_pid_uid_gid_map.cc deleted file mode 100644 index af052a63c..000000000 --- a/test/syscalls/linux/proc_pid_uid_gid_map.cc +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sched.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <functional> -#include <string> -#include <tuple> -#include <utility> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/ascii.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" -#include "test/util/time_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<int> InNewUserNamespace(const std::function<void()>& fn) { - return InForkedProcess([&] { - TEST_PCHECK(unshare(CLONE_NEWUSER) == 0); - MaybeSave(); - fn(); - }); -} - -PosixErrorOr<std::tuple<pid_t, Cleanup>> CreateProcessInNewUserNamespace() { - int pipefd[2]; - if (pipe(pipefd) < 0) { - return PosixError(errno, "pipe failed"); - } - const auto cleanup_pipe_read = - Cleanup([&] { EXPECT_THAT(close(pipefd[0]), SyscallSucceeds()); }); - auto cleanup_pipe_write = - Cleanup([&] { EXPECT_THAT(close(pipefd[1]), SyscallSucceeds()); }); - pid_t child_pid = fork(); - if (child_pid < 0) { - return PosixError(errno, "fork failed"); - } - if (child_pid == 0) { - // Close our copy of the pipe's read end, which doesn't really matter. - TEST_PCHECK(close(pipefd[0]) >= 0); - TEST_PCHECK(unshare(CLONE_NEWUSER) == 0); - MaybeSave(); - // Indicate that we've switched namespaces by unblocking the parent's read. - TEST_PCHECK(close(pipefd[1]) >= 0); - while (true) { - SleepSafe(absl::Minutes(1)); - } - } - auto cleanup_child = Cleanup([child_pid] { - EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << "status = " << status; - }); - // Close our copy of the pipe's write end, then wait for the child to close - // its copy, indicating that it's switched namespaces. - cleanup_pipe_write.Release()(); - char buf; - if (RetryEINTR(read)(pipefd[0], &buf, 1) < 0) { - return PosixError(errno, "reading from pipe failed"); - } - MaybeSave(); - return std::make_tuple(child_pid, std::move(cleanup_child)); -} - -// TEST_CHECK-fails on error, since this function is used in contexts that -// require async-signal-safety. -void DenySetgroupsByPath(const char* path) { - int fd = open(path, O_WRONLY); - if (fd < 0 && errno == ENOENT) { - // On kernels where this file doesn't exist, writing "deny" to it isn't - // necessary to write to gid_map. - return; - } - TEST_PCHECK(fd >= 0); - MaybeSave(); - char deny[] = "deny"; - TEST_PCHECK(write(fd, deny, sizeof(deny)) == sizeof(deny)); - MaybeSave(); - TEST_PCHECK(close(fd) == 0); -} - -void DenySelfSetgroups() { DenySetgroupsByPath("/proc/self/setgroups"); } - -void DenyPidSetgroups(pid_t pid) { - DenySetgroupsByPath(absl::StrCat("/proc/", pid, "/setgroups").c_str()); -} - -// Returns a valid UID/GID that isn't id. -uint32_t another_id(uint32_t id) { return (id + 1) % 65535; } - -struct TestParam { - std::string desc; - int cap; - std::function<std::string(absl::string_view)> get_map_filename; - std::function<uint32_t()> get_current_id; -}; - -std::string DescribeTestParam(const ::testing::TestParamInfo<TestParam>& info) { - return info.param.desc; -} - -std::vector<TestParam> UidGidMapTestParams() { - return {TestParam{"UID", CAP_SETUID, - [](absl::string_view pid) { - return absl::StrCat("/proc/", pid, "/uid_map"); - }, - []() -> uint32_t { return getuid(); }}, - TestParam{"GID", CAP_SETGID, - [](absl::string_view pid) { - return absl::StrCat("/proc/", pid, "/gid_map"); - }, - []() -> uint32_t { return getgid(); }}}; -} - -class ProcUidGidMapTest : public ::testing::TestWithParam<TestParam> { - protected: - uint32_t CurrentID() { return GetParam().get_current_id(); } -}; - -class ProcSelfUidGidMapTest : public ProcUidGidMapTest { - protected: - PosixErrorOr<int> InNewUserNamespaceWithMapFD( - const std::function<void(int)>& fn) { - std::string map_filename = GetParam().get_map_filename("self"); - return InNewUserNamespace([&] { - int fd = open(map_filename.c_str(), O_RDWR); - TEST_PCHECK(fd >= 0); - MaybeSave(); - fn(fd); - TEST_PCHECK(close(fd) == 0); - }); - } -}; - -class ProcPidUidGidMapTest : public ProcUidGidMapTest { - protected: - PosixErrorOr<bool> HaveSetIDCapability() { - return HaveCapability(GetParam().cap); - } - - // Returns true if the caller is running in a user namespace with all IDs - // mapped. This matters for tests that expect to successfully map arbitrary - // IDs into a child user namespace, since even with CAP_SET*ID this is only - // possible if those IDs are mapped into the current one. - PosixErrorOr<bool> AllIDsMapped() { - ASSIGN_OR_RETURN_ERRNO(std::string id_map, - GetContents(GetParam().get_map_filename("self"))); - absl::StripTrailingAsciiWhitespace(&id_map); - std::vector<std::string> id_map_parts = - absl::StrSplit(id_map, ' ', absl::SkipEmpty()); - return id_map_parts == std::vector<std::string>({"0", "0", "4294967295"}); - } - - PosixErrorOr<FileDescriptor> OpenMapFile(pid_t pid) { - return Open(GetParam().get_map_filename(absl::StrCat(pid)), O_RDWR); - } -}; - -TEST_P(ProcSelfUidGidMapTest, IsInitiallyEmpty) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - EXPECT_THAT(InNewUserNamespaceWithMapFD([](int fd) { - char buf[64]; - TEST_PCHECK(read(fd, buf, sizeof(buf)) == 0); - }), - IsPosixErrorOkAndHolds(0)); -} - -TEST_P(ProcSelfUidGidMapTest, IdentityMapOwnID) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - uint32_t id = CurrentID(); - std::string line = absl::StrCat(id, " ", id, " 1"); - EXPECT_THAT( - InNewUserNamespaceWithMapFD([&](int fd) { - DenySelfSetgroups(); - TEST_PCHECK(static_cast<long unsigned int>( - write(fd, line.c_str(), line.size())) == line.size()); - }), - IsPosixErrorOkAndHolds(0)); -} - -TEST_P(ProcSelfUidGidMapTest, TrailingNewlineAndNULIgnored) { - // This is identical to IdentityMapOwnID, except that a trailing newline, NUL, - // and an invalid (incomplete) map entry are appended to the valid entry. The - // newline should be accepted, and everything after the NUL should be ignored. - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - uint32_t id = CurrentID(); - std::string line = absl::StrCat(id, " ", id, " 1\n\0 4 3"); - EXPECT_THAT( - InNewUserNamespaceWithMapFD([&](int fd) { - DenySelfSetgroups(); - // The write should return the full size of the write, even though - // characters after the NUL were ignored. - TEST_PCHECK(static_cast<long unsigned int>( - write(fd, line.c_str(), line.size())) == line.size()); - }), - IsPosixErrorOkAndHolds(0)); -} - -TEST_P(ProcSelfUidGidMapTest, NonIdentityMapOwnID) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - uint32_t id = CurrentID(); - uint32_t id2 = another_id(id); - std::string line = absl::StrCat(id2, " ", id, " 1"); - EXPECT_THAT( - InNewUserNamespaceWithMapFD([&](int fd) { - DenySelfSetgroups(); - TEST_PCHECK(static_cast<long unsigned int>( - write(fd, line.c_str(), line.size())) == line.size()); - }), - IsPosixErrorOkAndHolds(0)); -} - -TEST_P(ProcSelfUidGidMapTest, MapOtherID) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - // Whether or not we have CAP_SET*ID is irrelevant: the process running in the - // new (child) user namespace won't have any capabilities in the current - // (parent) user namespace, which is needed. - uint32_t id = CurrentID(); - uint32_t id2 = another_id(id); - std::string line = absl::StrCat(id, " ", id2, " 1"); - EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) { - DenySelfSetgroups(); - TEST_PCHECK(write(fd, line.c_str(), line.size()) < 0); - TEST_CHECK(errno == EPERM); - }), - IsPosixErrorOkAndHolds(0)); -} - -INSTANTIATE_TEST_SUITE_P(All, ProcSelfUidGidMapTest, - ::testing::ValuesIn(UidGidMapTestParams()), - DescribeTestParam); - -TEST_P(ProcPidUidGidMapTest, MapOtherIDPrivileged) { - // Like ProcSelfUidGidMapTest_MapOtherID, but since we have CAP_SET*ID in the - // parent user namespace (this one), we can map IDs that aren't ours. - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability())); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped())); - - pid_t child_pid; - Cleanup cleanup_child; - std::tie(child_pid, cleanup_child) = - ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace()); - - uint32_t id = CurrentID(); - uint32_t id2 = another_id(id); - std::string line = absl::StrCat(id, " ", id2, " 1"); - DenyPidSetgroups(child_pid); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid)); - EXPECT_THAT(write(fd.get(), line.c_str(), line.size()), - SyscallSucceedsWithValue(line.size())); -} - -TEST_P(ProcPidUidGidMapTest, MapAnyIDsPrivileged) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability())); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped())); - - pid_t child_pid; - Cleanup cleanup_child; - std::tie(child_pid, cleanup_child) = - ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace()); - - // Test all of: - // - // - Mapping ranges of length > 1 - // - // - Mapping multiple ranges - // - // - Non-identity mappings - char entries[] = "2 0 2\n4 6 2"; - DenyPidSetgroups(child_pid); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid)); - EXPECT_THAT(write(fd.get(), entries, sizeof(entries)), - SyscallSucceedsWithValue(sizeof(entries))); -} - -INSTANTIATE_TEST_SUITE_P(All, ProcPidUidGidMapTest, - ::testing::ValuesIn(UidGidMapTestParams()), - DescribeTestParam); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/processes.cc b/test/syscalls/linux/processes.cc deleted file mode 100644 index 412582515..000000000 --- a/test/syscalls/linux/processes.cc +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdint.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/capability_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -int testSetPGIDOfZombie(void* arg) { - int p[2]; - - TEST_PCHECK(pipe(p) == 0); - - pid_t pid = fork(); - if (pid == 0) { - pid = fork(); - // Create a second child to repeat one of syzkaller reproducers. - if (pid == 0) { - pid = getpid(); - TEST_PCHECK(setpgid(pid, 0) == 0); - TEST_PCHECK(write(p[1], &pid, sizeof(pid)) == sizeof(pid)); - _exit(0); - } - TEST_PCHECK(pid > 0); - _exit(0); - } - close(p[1]); - TEST_PCHECK(pid > 0); - - // Get PID of the second child. - pid_t cpid; - TEST_PCHECK(read(p[0], &cpid, sizeof(cpid)) == sizeof(cpid)); - - // Wait when both child processes will die. - int c; - TEST_PCHECK(read(p[0], &c, sizeof(c)) == 0); - - // Wait the second child process to collect its zombie. - int status; - TEST_PCHECK(RetryEINTR(waitpid)(cpid, &status, 0) == cpid); - - // Set the child's group. - TEST_PCHECK(setpgid(pid, pid) == 0); - - TEST_PCHECK(RetryEINTR(waitpid)(-pid, &status, 0) == pid); - - TEST_PCHECK(status == 0); - _exit(0); -} - -TEST(Processes, SetPGIDOfZombie) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - // Fork a test process in a new PID namespace, because it needs to manipulate - // with reparanted processes. - struct clone_arg { - // Reserve some space for clone() to locate arguments and retcode in this - // place. - char stack[128] __attribute__((aligned(16))); - char stack_ptr[0]; - } ca; - pid_t pid; - ASSERT_THAT(pid = clone(testSetPGIDOfZombie, ca.stack_ptr, - CLONE_NEWPID | SIGCHLD, &ca), - SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_EQ(status, 0); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pselect.cc b/test/syscalls/linux/pselect.cc deleted file mode 100644 index 4e43c4d7f..000000000 --- a/test/syscalls/linux/pselect.cc +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/select.h> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/base_poll_test.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -struct MaskWithSize { - sigset_t* mask; - size_t mask_size; -}; - -// Linux and glibc have a different idea of the sizeof sigset_t. When calling -// the syscall directly, use what the kernel expects. -unsigned kSigsetSize = SIGRTMAX / 8; - -// Linux pselect(2) differs from the glibc wrapper function in that Linux -// updates the timeout with the amount of time remaining. In order to test this -// behavior we need to use the syscall directly. -int syscallPselect6(int nfds, fd_set* readfds, fd_set* writefds, - fd_set* exceptfds, struct timespec* timeout, - const MaskWithSize* mask_with_size) { - return syscall(SYS_pselect6, nfds, readfds, writefds, exceptfds, timeout, - mask_with_size); -} - -class PselectTest : public BasePollTest { - protected: - void SetUp() override { BasePollTest::SetUp(); } - void TearDown() override { BasePollTest::TearDown(); } -}; - -// See that when there are no FD sets, pselect behaves like sleep. -TEST_F(PselectTest, NullFds) { - struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10)); - ASSERT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, nullptr), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 0); - - timeout = absl::ToTimespec(absl::Milliseconds(10)); - ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 0); -} - -TEST_F(PselectTest, ClosedFds) { - fd_set read_set; - FD_ZERO(&read_set); - int fd; - ASSERT_THAT(fd = dup(1), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - FD_SET(fd, &read_set); - struct timespec timeout = absl::ToTimespec(absl::Milliseconds(10)); - EXPECT_THAT( - syscallPselect6(fd + 1, &read_set, nullptr, nullptr, &timeout, nullptr), - SyscallFailsWithErrno(EBADF)); -} - -TEST_F(PselectTest, ZeroTimeout) { - struct timespec timeout = {}; - ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 0); -} - -// If random S/R interrupts the pselect, SIGALRM may be delivered before pselect -// restarts, causing the pselect to hang forever. -TEST_F(PselectTest, NoTimeout_NoRandomSave) { - // When there's no timeout, pselect may never return so set a timer. - SetTimer(absl::Milliseconds(100)); - // See that we get interrupted by the timer. - ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, nullptr, nullptr), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); -} - -TEST_F(PselectTest, InvalidTimeoutNegative) { - struct timespec timeout = absl::ToTimespec(absl::Seconds(-1)); - ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr), - SyscallFailsWithErrno(EINVAL)); - EXPECT_EQ(timeout.tv_sec, -1); - EXPECT_EQ(timeout.tv_nsec, 0); -} - -TEST_F(PselectTest, InvalidTimeoutNotNormalized) { - struct timespec timeout = {0, 1000000001}; - ASSERT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, nullptr), - SyscallFailsWithErrno(EINVAL)); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_nsec, 1000000001); -} - -TEST_F(PselectTest, EmptySigMaskInvalidMaskSize) { - struct timespec timeout = {}; - MaskWithSize invalid = {nullptr, 7}; - EXPECT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, &invalid), - SyscallSucceeds()); -} - -TEST_F(PselectTest, EmptySigMaskValidMaskSize) { - struct timespec timeout = {}; - MaskWithSize invalid = {nullptr, 8}; - EXPECT_THAT(syscallPselect6(0, nullptr, nullptr, nullptr, &timeout, &invalid), - SyscallSucceeds()); -} - -TEST_F(PselectTest, InvalidMaskSize) { - struct timespec timeout = {}; - sigset_t sigmask; - ASSERT_THAT(sigemptyset(&sigmask), SyscallSucceeds()); - MaskWithSize invalid = {&sigmask, 7}; - EXPECT_THAT(syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &invalid), - SyscallFailsWithErrno(EINVAL)); -} - -// Verify that signals blocked by the pselect mask (that would otherwise be -// allowed) do not interrupt pselect. -TEST_F(PselectTest, SignalMaskBlocksSignal) { - absl::Duration duration(absl::Seconds(30)); - struct timespec timeout = absl::ToTimespec(duration); - absl::Duration timer_duration(absl::Seconds(10)); - - // Call with a mask that blocks SIGALRM. See that pselect is not interrupted - // (i.e. returns 0) and that upon completion, the timer has fired. - sigset_t mask; - ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds()); - ASSERT_THAT(sigaddset(&mask, SIGALRM), SyscallSucceeds()); - MaskWithSize mask_with_size = {&mask, kSigsetSize}; - SetTimer(timer_duration); - MaybeSave(); - ASSERT_FALSE(TimerFired()); - ASSERT_THAT( - syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &mask_with_size), - SyscallSucceeds()); - EXPECT_TRUE(TimerFired()); - EXPECT_EQ(absl::DurationFromTimespec(timeout), absl::Duration()); -} - -// Verify that signals allowed by the pselect mask (that would otherwise be -// blocked) interrupt pselect. -TEST_F(PselectTest, SignalMaskAllowsSignal) { - absl::Duration duration = absl::Seconds(30); - struct timespec timeout = absl::ToTimespec(duration); - absl::Duration timer_duration = absl::Seconds(10); - - sigset_t mask; - ASSERT_THAT(sigprocmask(0, nullptr, &mask), SyscallSucceeds()); - - // Block SIGALRM. - auto cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, SIGALRM)); - - // Call with a mask that unblocks SIGALRM. See that pselect is interrupted. - MaskWithSize mask_with_size = {&mask, kSigsetSize}; - SetTimer(timer_duration); - MaybeSave(); - ASSERT_FALSE(TimerFired()); - ASSERT_THAT( - syscallPselect6(1, nullptr, nullptr, nullptr, &timeout, &mask_with_size), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); - EXPECT_GT(absl::DurationFromTimespec(timeout), absl::Duration()); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc deleted file mode 100644 index d1d7c6f84..000000000 --- a/test/syscalls/linux/ptrace.cc +++ /dev/null @@ -1,2399 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <elf.h> -#include <signal.h> -#include <stddef.h> -#include <sys/prctl.h> -#include <sys/ptrace.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/user.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <iostream> -#include <utility> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/platform_util.h" -#include "test/util/signal_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/time_util.h" - -ABSL_FLAG(bool, ptrace_test_execve_child, false, - "If true, run the " - "PtraceExecveTest_Execve_GetRegs_PeekUser_SIGKILL_TraceClone_" - "TraceExit child workload."); -ABSL_FLAG(bool, ptrace_test_trace_descendants_allowed, false, - "If set, run the child workload for " - "PtraceTest_TraceDescendantsAllowed."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_pid, false, - "If set, run the child workload for PtraceTest_PrctlSetPtracerPID."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_any, false, - "If set, run the child workload for PtraceTest_PrctlSetPtracerAny."); -ABSL_FLAG(bool, ptrace_test_prctl_clear_ptracer, false, - "If set, run the child workload for PtraceTest_PrctlClearPtracer."); -ABSL_FLAG(bool, ptrace_test_prctl_replace_ptracer, false, - "If set, run the child workload for PtraceTest_PrctlReplacePtracer."); -ABSL_FLAG(int, ptrace_test_prctl_replace_ptracer_tid, -1, - "Specifies the replacement tracer tid in the child workload for " - "PtraceTest_PrctlReplacePtracer."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_and_exit_tracee_thread, false, - "If set, run the child workload for " - "PtraceTest_PrctlSetPtracerPersistsPastTraceeThreadExit."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_and_exec_non_leader, false, - "If set, run the child workload for " - "PtraceTest_PrctlSetPtracerDoesNotPersistPastNonLeaderExec."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_and_exit_tracer_thread, false, - "If set, run the child workload for " - "PtraceTest_PrctlSetPtracerDoesNotPersistPastTracerThreadExit."); -ABSL_FLAG(int, ptrace_test_prctl_set_ptracer_and_exit_tracer_thread_tid, -1, - "Specifies the tracee tid in the child workload for " - "PtraceTest_PrctlSetPtracerDoesNotPersistPastTracerThreadExit."); -ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_respects_tracer_thread_id, false, - "If set, run the child workload for PtraceTest_PrctlSetPtracePID."); -ABSL_FLAG(int, ptrace_test_prctl_set_ptracer_respects_tracer_thread_id_tid, -1, - "Specifies the thread tid to be traced in the child workload " - "for PtraceTest_PrctlSetPtracerRespectsTracerThreadID."); - -ABSL_FLAG(bool, ptrace_test_tracee, false, - "If true, run the tracee process for the " - "PrctlSetPtracerDoesNotPersistPastLeaderExec and " - "PrctlSetPtracerDoesNotPersistPastNonLeaderExec workloads."); -ABSL_FLAG(int, ptrace_test_trace_tid, -1, - "If set, run a process to ptrace attach to the thread with the " - "specified pid for the PrctlSetPtracerRespectsTracerThreadID " - "workload."); -ABSL_FLAG(int, ptrace_test_fd, -1, - "Specifies the fd used for communication between tracer and tracee " - "processes across exec."); - -namespace gvisor { -namespace testing { - -namespace { - -// PTRACE_GETSIGMASK and PTRACE_SETSIGMASK are not defined until glibc 2.23 -// (fb53a27c5741 "Add new header definitions from Linux 4.4 (plus older ptrace -// definitions)"). -constexpr auto kPtraceGetSigMask = static_cast<__ptrace_request>(0x420a); -constexpr auto kPtraceSetSigMask = static_cast<__ptrace_request>(0x420b); - -// PTRACE_SYSEMU is not defined until glibc 2.27 (c48831d0eebf "linux/x86: sync -// sys/ptrace.h with Linux 4.14 [BZ #22433]"). -constexpr auto kPtraceSysemu = static_cast<__ptrace_request>(31); - -// PTRACE_EVENT_STOP is not defined until glibc 2.26 (3f67d1a7021e "Add Linux -// PTRACE_EVENT_STOP"). -constexpr int kPtraceEventStop = 128; - -// Sends sig to the current process with tgkill(2). -// -// glibc's raise(2) may change the signal mask before sending the signal. These -// extra syscalls make tests of syscall, signal interception, etc. difficult to -// write. -void RaiseSignal(int sig) { - pid_t pid = getpid(); - TEST_PCHECK(pid > 0); - pid_t tid = gettid(); - TEST_PCHECK(tid > 0); - TEST_PCHECK(tgkill(pid, tid, sig) == 0); -} - -constexpr char kYamaPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope"; - -// Returns the Yama ptrace scope. -PosixErrorOr<int> YamaPtraceScope() { - ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(kYamaPtraceScopePath)); - if (!exists) { - // File doesn't exist means no Yama, so the scope is disabled -> 0. - return 0; - } - - std::string contents; - RETURN_IF_ERRNO(GetContents(kYamaPtraceScopePath, &contents)); - - int scope; - if (!absl::SimpleAtoi(contents, &scope)) { - return PosixError(EINVAL, absl::StrCat(contents, ": not a valid number")); - } - - return scope; -} - -int CheckPtraceAttach(pid_t pid) { - int ret = ptrace(PTRACE_ATTACH, pid, 0, 0); - MaybeSave(); - if (ret < 0) { - return ret; - } - - int status; - TEST_PCHECK(waitpid(pid, &status, 0) == pid); - MaybeSave(); - TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); - TEST_PCHECK(ptrace(PTRACE_DETACH, pid, 0, 0) == 0); - MaybeSave(); - return 0; -} - -TEST(PtraceTest, AttachSelf) { - EXPECT_THAT(ptrace(PTRACE_ATTACH, gettid(), 0, 0), - SyscallFailsWithErrno(EPERM)); -} - -TEST(PtraceTest, AttachSameThreadGroup) { - pid_t const tid = gettid(); - ScopedThread([&] { - EXPECT_THAT(ptrace(PTRACE_ATTACH, tid, 0, 0), SyscallFailsWithErrno(EPERM)); - }); -} - -TEST(PtraceTest, TraceParentNotAllowed) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) < 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_CHECK(CheckPtraceAttach(getppid()) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(PtraceTest, TraceNonDescendantNotAllowed) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) < 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - EXPECT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, TraceNonDescendantWithCapabilityAllowed) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE))); - // Skip if disallowed by YAMA despite having CAP_SYS_PTRACE. - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 2); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, TraceDescendantsAllowed) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use socket pair to communicate tids to this process from its grandchild. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_trace_descendants_allowed", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - TEST_PCHECK(close(sockets[1]) == 0); - pid_t const grandchild_pid = fork(); - if (grandchild_pid == 0) { - // This test will create a new thread in the grandchild process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - TEST_PCHECK(grandchild_pid > 0); - MaybeSave(); - - // Wait for grandchild. Our parent process will kill it once it's done. - int status; - TEST_PCHECK(waitpid(grandchild_pid, &status, 0) == grandchild_pid); - TEST_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL); - MaybeSave(); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - // We should be able to attach to any thread in the grandchild. - pid_t grandchild_tid1, grandchild_tid2; - ASSERT_THAT(read(sockets[1], &grandchild_tid1, sizeof(grandchild_tid1)), - SyscallSucceedsWithValue(sizeof(grandchild_tid1))); - ASSERT_THAT(read(sockets[1], &grandchild_tid2, sizeof(grandchild_tid2)), - SyscallSucceedsWithValue(sizeof(grandchild_tid2))); - - EXPECT_THAT(CheckPtraceAttach(grandchild_tid1), SyscallSucceeds()); - EXPECT_THAT(CheckPtraceAttach(grandchild_tid2), SyscallSucceeds()); - - // Clean up grandchild. - ASSERT_THAT(kill(grandchild_tid1, SIGKILL), SyscallSucceeds()); - - // Clean up child. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -[[noreturn]] void RunTraceDescendantsAllowed(int fd) { - // Let the tracer know our tid through the socket fd. - pid_t const tid = gettid(); - TEST_PCHECK(write(fd, &tid, sizeof(tid)) == sizeof(tid)); - MaybeSave(); - - ScopedThread t([fd] { - // See if any arbitrary thread (whose tid differs from the process id) can - // be traced as well. - pid_t const tid = gettid(); - TEST_PCHECK(write(fd, &tid, sizeof(tid)) == sizeof(tid)); - MaybeSave(); - while (true) { - SleepSafe(absl::Seconds(1)); - } - }); - - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -TEST(PtraceTest, PrctlSetPtracerInvalidPID) { - // EINVAL should also be returned if PR_SET_PTRACER is not supported. - EXPECT_THAT(prctl(PR_SET_PTRACER, 123456789), SyscallFailsWithErrno(EINVAL)); -} - -TEST(PtraceTest, PrctlSetPtracerPID) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_pid", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerPID(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -TEST(PtraceTest, PrctlSetPtracerAny) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_any", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerAny(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -TEST(PtraceTest, PrctlClearPtracer) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_clear_ptracer", "--ptrace_test_fd", - std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlClearPtracer(int fd) { - ScopedThread t([fd] { - // Perform prctl in a separate thread to verify that it is process-wide. - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - TEST_PCHECK(prctl(PR_SET_PTRACER, 0) == 0); - MaybeSave(); - // Indicate that the prctl has been set/cleared. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -TEST(PtraceTest, PrctlReplacePtracer) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - pid_t const unused_pid = fork(); - if (unused_pid == 0) { - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(unused_pid, SyscallSucceeds()); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", - "--ptrace_test_prctl_replace_ptracer", - "--ptrace_test_prctl_replace_ptracer_tid", - std::to_string(unused_pid), - "--ptrace_test_fd", - std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; - - // Clean up unused. - ASSERT_THAT(kill(unused_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(unused_pid, &status, 0), - SyscallSucceedsWithValue(unused_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlReplacePtracer(int new_tracer_pid, int fd) { - TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0); - MaybeSave(); - - ScopedThread t([new_tracer_pid, fd] { - TEST_PCHECK(prctl(PR_SET_PTRACER, new_tracer_pid) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - }); - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -// Tests that YAMA exceptions store tracees by thread group leader. Exceptions -// are preserved even after the tracee thread exits, as long as the tracee's -// thread group leader is still around. -TEST(PtraceTest, PrctlSetPtracerPersistsPastTraceeThreadExit) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", - "--ptrace_test_prctl_set_ptracer_and_exit_tracee_thread", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until the tracee thread calling prctl has terminated. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerPersistsPastTraceeThreadExit(int fd) { - ScopedThread t([] { - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - }); - t.Join(); - // Indicate that thread setting the prctl has exited. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -// Tests that YAMA exceptions store tracees by thread group leader. Exceptions -// are preserved across exec as long as the thread group leader does not change, -// even if the tracee thread is terminated. -TEST(PtraceTest, PrctlSetPtracerPersistsPastLeaderExec) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_tracee", "--ptrace_test_fd", - std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until the tracee has exec'd. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunTracee(int fd) { - // Indicate that we have exec'd. - TEST_PCHECK(write(fd, "x", 1) == 1); - MaybeSave(); - - while (true) { - SleepSafe(absl::Seconds(1)); - } -} - -// Tests that YAMA exceptions store tracees by thread group leader. Exceptions -// are cleared if the tracee process's thread group leader is terminated by -// exec. -TEST(PtraceTest, PrctlSetPtracerDoesNotPersistPastNonLeaderExec) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_and_exec_non_leader", - "--ptrace_test_fd", std::to_string(sockets[0])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - TEST_PCHECK(close(sockets[1]) == 0); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until the tracee has exec'd. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerDoesNotPersistPastNonLeaderExec(int fd) { - ScopedThread t([fd] { - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_tracee", "--ptrace_test_fd", - std::to_string(fd)}; - char* const* const child_argv = owned_child_argv.get(); - - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - }); - t.Join(); - TEST_CHECK_MSG(false, "Survived execve? (main)"); - _exit(1); -} - -// Tests that YAMA exceptions store the tracer itself rather than the thread -// group leader. Exceptions are cleared when the tracer task exits, rather than -// when its thread group leader exits. -TEST(PtraceTest, PrctlSetPtracerDoesNotPersistPastTracerThreadExit) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - pid_t tracer_tid; - TEST_PCHECK(read(sockets[0], &tracer_tid, sizeof(tracer_tid)) == - sizeof(tracer_tid)); - MaybeSave(); - - TEST_PCHECK(prctl(PR_SET_PTRACER, tracer_tid) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(sockets[0], "x", 1) == 1); - MaybeSave(); - - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", - "--ptrace_test_prctl_set_ptracer_and_exit_tracer_thread", - "--ptrace_test_prctl_set_ptracer_and_exit_tracer_thread_tid", - std::to_string(tracee_pid), - "--ptrace_test_fd", - std::to_string(sockets[1])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerDoesNotPersistPastTracerThreadExit( - int tracee_tid, int fd) { - TEST_PCHECK(SetCapability(CAP_SYS_PTRACE, false).ok()); - - ScopedThread t([fd] { - pid_t const tracer_tid = gettid(); - TEST_PCHECK(write(fd, &tracer_tid, sizeof(tracer_tid)) == - sizeof(tracer_tid)); - - // Wait until the prctl has been set. - char done; - TEST_PCHECK(read(fd, &done, 1) == 1); - MaybeSave(); - }); - t.Join(); - - // Sleep for a bit before verifying the invalidation. The thread exit above - // should cause the ptrace exception to be invalidated, but in Linux, this is - // not done immediately. The YAMA exception is dropped during - // __put_task_struct(), which occurs (at the earliest) one RCU grace period - // after exit_notify() ==> release_task(). - SleepSafe(absl::Milliseconds(100)); - - TEST_CHECK(CheckPtraceAttach(tracee_tid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); -} - -// Tests that YAMA exceptions store the tracer thread itself rather than the -// thread group leader. Exceptions are preserved across exec in the tracer -// thread, even if the thread group leader is terminated. -TEST(PtraceTest, PrctlSetPtracerRespectsTracerThreadID) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - pid_t tracer_tid; - TEST_PCHECK(read(sockets[0], &tracer_tid, sizeof(tracer_tid)) == - sizeof(tracer_tid)); - MaybeSave(); - - TEST_PCHECK(prctl(PR_SET_PTRACER, tracer_tid) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(sockets[0], "x", 1) == 1); - MaybeSave(); - - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - // Allocate vector before forking (not async-signal-safe). - ExecveArray const owned_child_argv = { - "/proc/self/exe", - "--ptrace_test_prctl_set_ptracer_respects_tracer_thread_id", - "--ptrace_test_prctl_set_ptracer_respects_tracer_thread_id_tid", - std::to_string(tracee_pid), - "--ptrace_test_fd", - std::to_string(sockets[1])}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // This test will create a new thread in the child process. - // pthread_create(2) isn't async-signal-safe, so we execve() first. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunPrctlSetPtracerRespectsTracerThreadID(int tracee_tid, - int fd) { - // Create a separate thread for tracing (i.e., not the thread group - // leader). After the subsequent execve(), the current thread group leader - // will no longer be exist, but the YAMA exception installed with this - // thread should still be valid. - ScopedThread t([tracee_tid, fd] { - pid_t const tracer_tid = gettid(); - TEST_PCHECK(write(fd, &tracer_tid, sizeof(tracer_tid))); - MaybeSave(); - - // Wait until the tracee has made the PR_SET_PTRACER prctl. - char done; - TEST_PCHECK(read(fd, &done, 1) == 1); - MaybeSave(); - - ExecveArray const owned_child_argv = { - "/proc/self/exe", "--ptrace_test_trace_tid", std::to_string(tracee_tid), - "--ptrace_test_fd", std::to_string(fd)}; - char* const* const child_argv = owned_child_argv.get(); - - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - }); - t.Join(); - TEST_CHECK_MSG(false, "Survived execve? (main)"); - _exit(1); -} - -[[noreturn]] void RunTraceTID(int tracee_tid, int fd) { - TEST_PCHECK(SetCapability(CAP_SYS_PTRACE, false).ok()); - TEST_PCHECK(CheckPtraceAttach(tracee_tid) == 0); - _exit(0); -} - -// Tests that removing a YAMA exception does not affect a tracer that is already -// attached. -TEST(PtraceTest, PrctlClearPtracerDoesNotAffectCurrentTracer) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Use sockets to synchronize between tracer and tracee. - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - TEST_PCHECK(close(sockets[1]) == 0); - TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(sockets[0], "x", 1) == 1); - MaybeSave(); - - // Wait until tracer has attached before clearing PR_SET_PTRACER. - char done; - TEST_PCHECK(read(sockets[0], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(prctl(PR_SET_PTRACER, 0) == 0); - MaybeSave(); - // Indicate that the prctl has been set. - TEST_PCHECK(write(sockets[0], "x", 1) == 1); - MaybeSave(); - - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - ASSERT_THAT(close(sockets[0]), SyscallSucceeds()); - - std::string mem_path = "/proc/" + std::to_string(tracee_pid) + "/mem"; - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - // Wait until tracee has called prctl, or else we won't be able to attach. - char done; - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - - TEST_PCHECK(ptrace(PTRACE_ATTACH, tracee_pid, 0, 0) == 0); - MaybeSave(); - // Indicate that we have attached. - TEST_PCHECK(write(sockets[1], &done, 1) == 1); - MaybeSave(); - - // Block until tracee enters signal-delivery-stop as a result of the - // SIGSTOP sent by PTRACE_ATTACH. - int status; - TEST_PCHECK(waitpid(tracee_pid, &status, 0) == tracee_pid); - MaybeSave(); - TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); - MaybeSave(); - - TEST_PCHECK(ptrace(PTRACE_CONT, tracee_pid, 0, 0) == 0); - MaybeSave(); - - // Wait until tracee has cleared PR_SET_PTRACER. Even though it was cleared, - // we should still be able to access /proc/[pid]/mem because we are already - // attached. - TEST_PCHECK(read(sockets[1], &done, 1) == 1); - MaybeSave(); - TEST_PCHECK(open(mem_path.c_str(), O_RDONLY) != -1); - MaybeSave(); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, PrctlNotInherited) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1); - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - - // Allow any ptracer. This should not affect the child processes. - ASSERT_THAT(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY), SyscallSucceeds()); - - pid_t const tracee_pid = fork(); - if (tracee_pid == 0) { - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - ASSERT_THAT(tracee_pid, SyscallSucceeds()); - - pid_t const tracer_pid = fork(); - if (tracer_pid == 0) { - TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1); - TEST_PCHECK(errno == EPERM); - _exit(0); - } - ASSERT_THAT(tracer_pid, SyscallSucceeds()); - - // Clean up tracer. - int status; - ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Clean up tracee. - ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(tracee_pid, &status, 0), - SyscallSucceedsWithValue(tracee_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) { - // Yama prevents attaching to a parent. Skip the test if the scope is anything - // except disabled. - const int yama_scope = ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()); - SKIP_IF(yama_scope > 1); - if (yama_scope == 1) { - // Allow child to trace us. - ASSERT_THAT(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY), SyscallSucceeds()); - } - - // Test PTRACE_POKE/PEEKDATA on both anonymous and file mappings. - const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_NO_ERRNO(Truncate(file.path(), kPageSize)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - const auto file_mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap( - nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); - - constexpr long kBeforePokeDataAnonValue = 10; - constexpr long kAfterPokeDataAnonValue = 20; - constexpr long kBeforePokeDataFileValue = 0; // implicit, due to truncate() - constexpr long kAfterPokeDataFileValue = 30; - - volatile long anon_word = kBeforePokeDataAnonValue; - auto* file_word_ptr = static_cast<volatile long*>(file_mapping.ptr()); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Attach to the parent. - pid_t const parent_pid = getppid(); - TEST_PCHECK(ptrace(PTRACE_ATTACH, parent_pid, 0, 0) == 0); - MaybeSave(); - - // Block until the parent enters signal-delivery-stop as a result of the - // SIGSTOP sent by PTRACE_ATTACH. - int status; - TEST_PCHECK(waitpid(parent_pid, &status, 0) == parent_pid); - MaybeSave(); - TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); - - // Replace the value of anon_word in the parent process with - // kAfterPokeDataAnonValue. - long parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &anon_word, 0); - MaybeSave(); - TEST_CHECK(parent_word == kBeforePokeDataAnonValue); - TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, &anon_word, - kAfterPokeDataAnonValue) == 0); - MaybeSave(); - - // Replace the value pointed to by file_word_ptr in the mapped file with - // kAfterPokeDataFileValue, via the parent process' mapping. - parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, file_word_ptr, 0); - MaybeSave(); - TEST_CHECK(parent_word == kBeforePokeDataFileValue); - TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, file_word_ptr, - kAfterPokeDataFileValue) == 0); - MaybeSave(); - - // Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not - // suppressed, the parent will hang in group-stop, causing the test to time - // out. - TEST_PCHECK(ptrace(PTRACE_DETACH, parent_pid, 0, 0) == 0); - MaybeSave(); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to complete. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Check that the child's PTRACE_POKEDATA was effective. - EXPECT_EQ(kAfterPokeDataAnonValue, anon_word); - EXPECT_EQ(kAfterPokeDataFileValue, *file_word_ptr); -} - -TEST(PtraceTest, GetSigMask) { - // glibc and the Linux kernel define a sigset_t with different sizes. To avoid - // creating a kernel_sigset_t and recreating all the modification functions - // (sigemptyset, etc), we just hardcode the kernel sigset size. - constexpr int kSizeofKernelSigset = 8; - constexpr int kBlockSignal = SIGUSR1; - sigset_t blocked; - sigemptyset(&blocked); - sigaddset(&blocked, kBlockSignal); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Install a signal handler for kBlockSignal to avoid termination and block - // it. - TEST_PCHECK(signal( - kBlockSignal, +[](int signo) {}) != SIG_ERR); - MaybeSave(); - TEST_PCHECK(sigprocmask(SIG_SETMASK, &blocked, nullptr) == 0); - MaybeSave(); - - // Enable tracing. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - - // This should be blocked. - RaiseSignal(kBlockSignal); - - // This should be suppressed by parent, who will change signal mask in the - // meantime, which means kBlockSignal should be delivered once this resumes. - RaiseSignal(SIGSTOP); - - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Get current signal mask. - sigset_t set; - EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, kSizeofKernelSigset, &set), - SyscallSucceeds()); - EXPECT_THAT(blocked, EqualsSigset(set)); - - // Try to get current signal mask with bad size argument. - EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, 0, nullptr), - SyscallFailsWithErrno(EINVAL)); - - // Try to set bad signal mask. - sigset_t* bad_addr = reinterpret_cast<sigset_t*>(-1); - EXPECT_THAT( - ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, bad_addr), - SyscallFailsWithErrno(EFAULT)); - - // Set signal mask to empty set. - sigset_t set1; - sigemptyset(&set1); - EXPECT_THAT(ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, &set1), - SyscallSucceeds()); - - // Suppress SIGSTOP and resume the child. It should re-enter - // signal-delivery-stop for kBlockSignal. - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kBlockSignal) - << " status " << status; - - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - // Let's see that process exited normally. - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(PtraceTest, GetSiginfo_SetSiginfo_SignalInjection) { - constexpr int kOriginalSigno = SIGUSR1; - constexpr int kInjectedSigno = SIGUSR2; - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Override all signal handlers. - struct sigaction sa = {}; - sa.sa_handler = +[](int signo) { _exit(signo); }; - TEST_PCHECK(sigfillset(&sa.sa_mask) == 0); - for (int signo = 1; signo < 32; signo++) { - if (signo == SIGKILL || signo == SIGSTOP) { - continue; - } - TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0); - } - for (int signo = SIGRTMIN; signo <= SIGRTMAX; signo++) { - TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0); - } - - // Unblock all signals. - TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0); - MaybeSave(); - - // Send ourselves kOriginalSignal while ptraced and exit with the signal we - // actually receive via the signal handler, if any, or 0 if we don't receive - // a signal. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - RaiseSignal(kOriginalSigno); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself kOriginalSigno and enter - // signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno) - << " status " << status; - - siginfo_t siginfo = {}; - ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo), - SyscallSucceeds()); - EXPECT_EQ(kOriginalSigno, siginfo.si_signo); - EXPECT_EQ(SI_TKILL, siginfo.si_code); - - // Replace the signal with kInjectedSigno, and check that the child exits - // with kInjectedSigno, indicating that signal injection was successful. - siginfo.si_signo = kInjectedSigno; - ASSERT_THAT(ptrace(PTRACE_SETSIGINFO, child_pid, 0, &siginfo), - SyscallSucceeds()); - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, kInjectedSigno), - SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == kInjectedSigno) - << " status " << status; -} - -TEST(PtraceTest, SIGKILLDoesNotCauseSignalDeliveryStop) { - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - RaiseSignal(SIGKILL); - TEST_CHECK_MSG(false, "Survived SIGKILL?"); - _exit(1); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Expect the child to die to SIGKILL without entering signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, PtraceKill) { - constexpr int kOriginalSigno = SIGUSR1; - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - - // PTRACE_KILL only works if tracee has entered signal-delivery-stop. - RaiseSignal(kOriginalSigno); - TEST_CHECK_MSG(false, "Failed to kill the process?"); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself kOriginalSigno and enter - // signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno) - << " status " << status; - - ASSERT_THAT(ptrace(PTRACE_KILL, child_pid, 0, 0), SyscallSucceeds()); - - // Expect the child to die with SIGKILL. - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, GetRegSet) { - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Enable tracing. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - - // Use kill explicitly because we check the syscall argument register below. - kill(getpid(), SIGSTOP); - - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Get the general registers. - struct user_regs_struct regs; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &iov), - SyscallSucceeds()); - - // Read exactly the full register set. - EXPECT_EQ(iov.iov_len, sizeof(regs)); - -#if defined(__x86_64__) - // Child called kill(2), with SIGSTOP as arg 2. - EXPECT_EQ(regs.rsi, SIGSTOP); -#elif defined(__aarch64__) - EXPECT_EQ(regs.regs[1], SIGSTOP); -#endif - - // Suppress SIGSTOP and resume the child. - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - // Let's see that process exited normally. - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(PtraceTest, AttachingConvertsGroupStopToPtraceStop) { - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - while (true) { - pause(); - } - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // SIGSTOP the child and wait for it to stop. - ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Attach to the child and expect it to re-enter a traced group-stop despite - // already being stopped. - ASSERT_THAT(ptrace(PTRACE_ATTACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Verify that the child is ptrace-stopped by checking that it can receive - // ptrace commands requiring a ptrace-stop. - EXPECT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, 0), SyscallSucceeds()); - - // Group-stop is distinguished from signal-delivery-stop by PTRACE_GETSIGINFO - // failing with EINVAL. - siginfo_t siginfo = {}; - EXPECT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo), - SyscallFailsWithErrno(EINVAL)); - - // Detach from the child and expect it to stay stopped without a notification. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED | WNOHANG), - SyscallSucceedsWithValue(0)); - - // Sending it SIGCONT should cause it to leave its stop. - ASSERT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, WCONTINUED), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFCONTINUED(status)) << " status " << status; - - // Clean up the child. - ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -// Fixture for tests parameterized by whether or not to use PTRACE_O_TRACEEXEC. -class PtraceExecveTest : public ::testing::TestWithParam<bool> { - protected: - bool TraceExec() const { return GetParam(); } -}; - -TEST_P(PtraceExecveTest, Execve_GetRegs_PeekUser_SIGKILL_TraceClone_TraceExit) { - ExecveArray const owned_child_argv = {"/proc/self/exe", - "--ptrace_test_execve_child"}; - char* const* const child_argv = owned_child_argv.get(); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. The test relies on calling execve() in a non-leader - // thread; pthread_create() isn't async-signal-safe, so the safest way to - // do this is to execve() first, then enable tracing and run the expected - // child process behavior in the new subprocess. - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve to test child"); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Enable PTRACE_O_TRACECLONE so we can get the ID of the child's non-leader - // thread, PTRACE_O_TRACEEXIT so we can observe the leader's death, and - // PTRACE_O_TRACEEXEC if required by the test. (The leader doesn't call - // execve, but options should be inherited across clone.) - long opts = PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT; - if (TraceExec()) { - opts |= PTRACE_O_TRACEEXEC; - } - ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, opts), SyscallSucceeds()); - - // Suppress the SIGSTOP and wait for the child's leader thread to report - // PTRACE_EVENT_CLONE. Get the new thread's ID from the event. - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_CLONE << 8), status >> 8); - unsigned long eventmsg; - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg), - SyscallSucceeds()); - pid_t const nonleader_tid = eventmsg; - pid_t const leader_tid = child_pid; - - // The new thread should be ptraced and in signal-delivery-stop by SIGSTOP due - // to PTRACE_O_TRACECLONE. - // - // Before bf959931ddb88c4e4366e96dd22e68fa0db9527c "wait/ptrace: assume __WALL - // if the child is traced" (4.7) , waiting on it requires __WCLONE since, as a - // non-leader, its termination signal is 0. After, a standard wait is - // sufficient. - ASSERT_THAT(waitpid(nonleader_tid, &status, __WCLONE), - SyscallSucceedsWithValue(nonleader_tid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Resume both child threads. - for (pid_t const tid : {leader_tid, nonleader_tid}) { - ASSERT_THAT(ptrace(PTRACE_CONT, tid, 0, 0), SyscallSucceeds()); - } - - // The non-leader child thread should call execve, causing the leader thread - // to enter PTRACE_EVENT_EXIT with an apparent exit code of 0. At this point, - // the leader has not yet exited, so the non-leader should be blocked in - // execve. - ASSERT_THAT(waitpid(leader_tid, &status, 0), - SyscallSucceedsWithValue(leader_tid)); - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8); - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg), - SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(eventmsg) && WEXITSTATUS(eventmsg) == 0) - << " eventmsg " << eventmsg; - EXPECT_THAT(waitpid(nonleader_tid, &status, __WCLONE | WNOHANG), - SyscallSucceedsWithValue(0)); - - // Allow the leader to continue exiting. This should allow the non-leader to - // complete its execve, causing the original leader to be reaped without - // further notice and the non-leader to steal its ID. - ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(leader_tid, &status, 0), - SyscallSucceedsWithValue(leader_tid)); - if (TraceExec()) { - // If PTRACE_O_TRACEEXEC was enabled, the execing thread should be in - // PTRACE_EVENT_EXEC-stop, with the event message set to its old thread ID. - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXEC << 8), status >> 8); - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg), - SyscallSucceeds()); - EXPECT_EQ(nonleader_tid, eventmsg); - } else { - // Otherwise, the execing thread should have received SIGTRAP and should now - // be in signal-delivery-stop. - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << " status " << status; - } - -#ifdef __x86_64__ - { - // CS should be 0x33, indicating an 64-bit binary. - constexpr uint64_t kAMD64UserCS = 0x33; - EXPECT_THAT(ptrace(PTRACE_PEEKUSER, leader_tid, - offsetof(struct user_regs_struct, cs), 0), - SyscallSucceedsWithValue(kAMD64UserCS)); - struct user_regs_struct regs = {}; - ASSERT_THAT(ptrace(PTRACE_GETREGS, leader_tid, 0, ®s), - SyscallSucceeds()); - EXPECT_EQ(kAMD64UserCS, regs.cs); - } -#endif // defined(__x86_64__) - - // PTRACE_O_TRACEEXIT should have been inherited across execve. Send SIGKILL, - // which should end the PTRACE_EVENT_EXEC-stop or signal-delivery-stop and - // leave the child in PTRACE_EVENT_EXIT-stop. - ASSERT_THAT(kill(leader_tid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(leader_tid, &status, 0), - SyscallSucceedsWithValue(leader_tid)); - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8); - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg), - SyscallSucceeds()); - EXPECT_TRUE(WIFSIGNALED(eventmsg) && WTERMSIG(eventmsg) == SIGKILL) - << " eventmsg " << eventmsg; - - // End the PTRACE_EVENT_EXIT stop, allowing the child to exit. - ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(leader_tid, &status, 0), - SyscallSucceedsWithValue(leader_tid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -[[noreturn]] void RunExecveChild() { - // Enable tracing, then raise SIGSTOP and expect our parent to suppress it. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - RaiseSignal(SIGSTOP); - MaybeSave(); - - // Call execve() in a non-leader thread. As long as execve() succeeds, what - // exactly we execve() shouldn't really matter, since the tracer should kill - // us after execve() completes. - ScopedThread t([&] { - ExecveArray const owned_child_argv = {"/proc/self/exe", - "--this_flag_shouldnt_exist"}; - char* const* const child_argv = owned_child_argv.get(); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "Survived execve? (thread)"); - }); - t.Join(); - TEST_CHECK_MSG(false, "Survived execve? (main)"); - _exit(1); -} - -INSTANTIATE_TEST_SUITE_P(TraceExec, PtraceExecveTest, ::testing::Bool()); - -// This test has expectations on when syscall-enter/exit-stops occur that are -// violated if saving occurs, since saving interrupts all syscalls, causing -// premature syscall-exit. -TEST(PtraceTest, - ExitWhenParentIsNotTracer_Syscall_TraceVfork_TraceVforkDone_NoRandomSave) { - constexpr int kExitTraceeExitCode = 99; - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Block SIGCHLD so it doesn't interrupt wait4. - sigset_t mask; - TEST_PCHECK(sigemptyset(&mask) == 0); - TEST_PCHECK(sigaddset(&mask, SIGCHLD) == 0); - TEST_PCHECK(sigprocmask(SIG_SETMASK, &mask, nullptr) == 0); - MaybeSave(); - - // Enable tracing, then raise SIGSTOP and expect our parent to suppress it. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - RaiseSignal(SIGSTOP); - MaybeSave(); - - // Spawn a vfork child that exits immediately, and reap it. Don't save - // after vfork since the parent expects to see wait4 as the next syscall. - pid_t const pid = vfork(); - if (pid == 0) { - _exit(kExitTraceeExitCode); - } - TEST_PCHECK_MSG(pid > 0, "vfork failed"); - - int status; - TEST_PCHECK(wait4(pid, &status, 0, nullptr) > 0); - MaybeSave(); - TEST_CHECK(WIFEXITED(status) && WEXITSTATUS(status) == kExitTraceeExitCode); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(child_pid, SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Enable PTRACE_O_TRACEVFORK so we can get the ID of the grandchild, - // PTRACE_O_TRACEVFORKDONE so we can observe PTRACE_EVENT_VFORK_DONE, and - // PTRACE_O_TRACESYSGOOD so syscall-enter/exit-stops are unambiguously - // indicated by a stop signal of SIGTRAP|0x80 rather than just SIGTRAP. - ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, - PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | - PTRACE_O_TRACESYSGOOD), - SyscallSucceeds()); - - // Suppress the SIGSTOP and wait for the child to report PTRACE_EVENT_VFORK. - // Get the new process' ID from the event. - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK << 8), status >> 8); - unsigned long eventmsg; - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg), - SyscallSucceeds()); - pid_t const grandchild_pid = eventmsg; - - // The grandchild should be traced by us and in signal-delivery-stop by - // SIGSTOP due to PTRACE_O_TRACEVFORK. This allows us to wait on it even - // though we're not its parent. - ASSERT_THAT(waitpid(grandchild_pid, &status, 0), - SyscallSucceedsWithValue(grandchild_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Resume the child with PTRACE_SYSCALL. Since the grandchild is still in - // signal-delivery-stop, the child should remain in vfork() waiting for the - // grandchild to exec or exit. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); - ASSERT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Suppress the grandchild's SIGSTOP and wait for the grandchild to exit. Pass - // WNOWAIT to waitid() so that we don't acknowledge the grandchild's exit yet. - ASSERT_THAT(ptrace(PTRACE_CONT, grandchild_pid, 0, 0), SyscallSucceeds()); - siginfo_t siginfo = {}; - ASSERT_THAT(waitid(P_PID, grandchild_pid, &siginfo, WEXITED | WNOWAIT), - SyscallSucceeds()); - EXPECT_EQ(SIGCHLD, siginfo.si_signo); - EXPECT_EQ(CLD_EXITED, siginfo.si_code); - EXPECT_EQ(kExitTraceeExitCode, siginfo.si_status); - EXPECT_EQ(grandchild_pid, siginfo.si_pid); - EXPECT_EQ(getuid(), siginfo.si_uid); - - // The child should now be in PTRACE_EVENT_VFORK_DONE stop. The event - // message should still be the grandchild's PID. - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8), status >> 8); - ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg), - SyscallSucceeds()); - EXPECT_EQ(grandchild_pid, eventmsg); - - // Resume the child with PTRACE_SYSCALL again and expect it to enter - // syscall-exit-stop for vfork() or clone(), either of which should return the - // grandchild's PID from the syscall. Aside from PTRACE_O_TRACESYSGOOD, - // syscall-stops are distinguished from signal-delivery-stop by - // PTRACE_GETSIGINFO returning a siginfo for which si_code == SIGTRAP or - // SIGTRAP|0x80. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) - << " status " << status; - ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo), - SyscallSucceeds()); - EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80)) - << "si_code = " << siginfo.si_code; - - { - struct user_regs_struct regs = {}; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &iov), - SyscallSucceeds()); -#if defined(__x86_64__) - EXPECT_TRUE(regs.orig_rax == SYS_vfork || regs.orig_rax == SYS_clone) - << "orig_rax = " << regs.orig_rax; - EXPECT_EQ(grandchild_pid, regs.rax); -#elif defined(__aarch64__) - EXPECT_TRUE(regs.regs[8] == SYS_clone) << "regs[8] = " << regs.regs[8]; - EXPECT_EQ(grandchild_pid, regs.regs[0]); -#endif // defined(__x86_64__) - } - - // After this point, the child will be making wait4 syscalls that will be - // interrupted by saving, so saving is not permitted. Note that this is - // explicitly released below once the grandchild exits. - DisableSave ds; - - // Resume the child with PTRACE_SYSCALL again and expect it to enter - // syscall-enter-stop for wait4(). - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) - << " status " << status; - ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo), - SyscallSucceeds()); - EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80)) - << "si_code = " << siginfo.si_code; -#ifdef __x86_64__ - { - EXPECT_THAT(ptrace(PTRACE_PEEKUSER, child_pid, - offsetof(struct user_regs_struct, orig_rax), 0), - SyscallSucceedsWithValue(SYS_wait4)); - } -#endif // defined(__x86_64__) - - // Resume the child with PTRACE_SYSCALL again. Since the grandchild is - // waiting for the tracer (us) to acknowledge its exit first, wait4 should - // block. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); - ASSERT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Acknowledge the grandchild's exit. - ASSERT_THAT(waitpid(grandchild_pid, &status, 0), - SyscallSucceedsWithValue(grandchild_pid)); - ds.reset(); - - // Now the child should enter syscall-exit-stop for wait4, returning with the - // grandchild's PID. - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) - << " status " << status; - { - struct user_regs_struct regs = {}; - struct iovec iov; - iov.iov_base = ®s; - iov.iov_len = sizeof(regs); - EXPECT_THAT(ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &iov), - SyscallSucceeds()); -#if defined(__x86_64__) - EXPECT_EQ(SYS_wait4, regs.orig_rax); - EXPECT_EQ(grandchild_pid, regs.rax); -#elif defined(__aarch64__) - EXPECT_EQ(SYS_wait4, regs.regs[8]); - EXPECT_EQ(grandchild_pid, regs.regs[0]); -#endif // defined(__x86_64__) - } - - // Detach from the child and wait for it to exit. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -// These tests requires knowledge of architecture-specific syscall convention. -#ifdef __x86_64__ -TEST(PtraceTest, Int3) { - SKIP_IF(PlatformSupportInt3() == PlatformSupport::NotSupported); - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Enable tracing. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - - // Interrupt 3 - trap to debugger - asm("int3"); - - _exit(56); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << " status " << status; - - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - - // The child should validate the injected return value and then exit normally. - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 56) - << " status " << status; -} - -TEST(PtraceTest, Sysemu_PokeUser) { - constexpr int kSysemuHelperFirstExitCode = 126; - constexpr uint64_t kSysemuInjectedExitGroupReturn = 42; - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Enable tracing, then raise SIGSTOP and expect our parent to suppress it. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - RaiseSignal(SIGSTOP); - - // Try to exit_group, expecting the tracer to skip the syscall and set its - // own return value. - int const rv = syscall(SYS_exit_group, kSysemuHelperFirstExitCode); - TEST_PCHECK_MSG(rv == kSysemuInjectedExitGroupReturn, - "exit_group returned incorrect value"); - - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop - // for its first exit_group syscall. - ASSERT_THAT(ptrace(kPtraceSysemu, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << " status " << status; - - struct user_regs_struct regs = {}; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, ®s), SyscallSucceeds()); - EXPECT_EQ(SYS_exit_group, regs.orig_rax); - EXPECT_EQ(-ENOSYS, regs.rax); - EXPECT_EQ(kSysemuHelperFirstExitCode, regs.rdi); - - // Replace the exit_group return value, then resume the child, which should - // automatically skip the syscall. - ASSERT_THAT( - ptrace(PTRACE_POKEUSER, child_pid, offsetof(struct user_regs_struct, rax), - kSysemuInjectedExitGroupReturn), - SyscallSucceeds()); - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - - // The child should validate the injected return value and then exit normally. - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -// This test also cares about syscall-exit-stop. -TEST(PtraceTest, ERESTART_NoRandomSave) { - constexpr int kSigno = SIGUSR1; - - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - - // Ignore, but unblock, kSigno. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - TEST_PCHECK(sigfillset(&sa.sa_mask) == 0); - TEST_PCHECK(sigaction(kSigno, &sa, nullptr) == 0); - MaybeSave(); - TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0); - MaybeSave(); - - // Enable tracing, then raise SIGSTOP and expect our parent to suppress it. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - RaiseSignal(SIGSTOP); - - // Invoke the pause syscall, which normally should not return until we - // receive a signal that "either terminates the process or causes the - // invocation of a signal-catching function". - pause(); - - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // After this point, the child's pause syscall will be interrupted by saving, - // so saving is not permitted. Note that this is explicitly released below - // once the child is stopped. - DisableSave ds; - - // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop for - // its pause syscall. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << " status " << status; - - struct user_regs_struct regs = {}; - ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, ®s), SyscallSucceeds()); - EXPECT_EQ(SYS_pause, regs.orig_rax); - EXPECT_EQ(-ENOSYS, regs.rax); - - // Resume the child with PTRACE_SYSCALL and expect it to block in the pause - // syscall. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); - ASSERT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Send the child kSigno, causing it to return ERESTARTNOHAND and enter - // syscall-exit-stop from the pause syscall. - constexpr int ERESTARTNOHAND = 514; - ASSERT_THAT(kill(child_pid, kSigno), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) - << " status " << status; - ds.reset(); - - ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, ®s), SyscallSucceeds()); - EXPECT_EQ(SYS_pause, regs.orig_rax); - EXPECT_EQ(-ERESTARTNOHAND, regs.rax); - - // Replace the return value from pause with 0, causing pause to not be - // restarted despite kSigno being ignored. - ASSERT_THAT(ptrace(PTRACE_POKEUSER, child_pid, - offsetof(struct user_regs_struct, rax), 0), - SyscallSucceeds()); - - // Detach from the child and wait for it to exit. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} -#endif // defined(__x86_64__) - -TEST(PtraceTest, Seize_Interrupt_Listen) { - volatile long child_should_spin = 1; - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - while (child_should_spin) { - SleepSafe(absl::Seconds(1)); - } - _exit(1); - } - - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Attach to the child with PTRACE_SEIZE; doing so should not stop the child. - ASSERT_THAT(ptrace(PTRACE_SEIZE, child_pid, 0, 0), SyscallSucceeds()); - int status; - EXPECT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Stop the child with PTRACE_INTERRUPT. - ASSERT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8); - - // Unset child_should_spin to verify that the child never leaves the spin - // loop. - ASSERT_THAT(ptrace(PTRACE_POKEDATA, child_pid, &child_should_spin, 0), - SyscallSucceeds()); - - // Send SIGSTOP to the child, then resume it, allowing it to proceed to - // signal-delivery-stop. - ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds()); - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // Release the child from signal-delivery-stop without suppressing the - // SIGSTOP, causing it to enter group-stop. - ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, SIGSTOP), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGSTOP | (kPtraceEventStop << 8), status >> 8); - - // "The state of the tracee after PTRACE_LISTEN is somewhat of a gray area: it - // is not in any ptrace-stop (ptrace commands won't work on it, and it will - // deliver waitpid(2) notifications), but it also may be considered 'stopped' - // because it is not executing instructions (is not scheduled), and if it was - // in group-stop before PTRACE_LISTEN, it will not respond to signals until - // SIGCONT is received." - ptrace(2). - ASSERT_THAT(ptrace(PTRACE_LISTEN, child_pid, 0, 0), SyscallSucceeds()); - EXPECT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), - SyscallFailsWithErrno(ESRCH)); - EXPECT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(kill(child_pid, SIGTERM), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); - EXPECT_THAT(waitpid(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Send SIGCONT to the child, causing it to leave group-stop and re-trap due - // to PTRACE_LISTEN. - EXPECT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8); - - // Detach the child and expect it to exit due to the SIGTERM we sent while - // it was stopped by PTRACE_LISTEN. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) - << " status " << status; -} - -TEST(PtraceTest, Interrupt_Listen_RequireSeize) { - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - raise(SIGSTOP); - _exit(0); - } - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop. - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) - << " status " << status; - - // PTRACE_INTERRUPT and PTRACE_LISTEN should fail since the child wasn't - // attached with PTRACE_SEIZE, leaving the child in signal-delivery-stop. - EXPECT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0), - SyscallFailsWithErrno(EIO)); - EXPECT_THAT(ptrace(PTRACE_LISTEN, child_pid, 0, 0), - SyscallFailsWithErrno(EIO)); - - // Suppress SIGSTOP and detach from the child, expecting it to exit normally. - ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(PtraceTest, SeizeSetOptions) { - pid_t const child_pid = fork(); - if (child_pid == 0) { - // In child process. - while (true) { - SleepSafe(absl::Seconds(1)); - } - } - - // In parent process. - ASSERT_THAT(child_pid, SyscallSucceeds()); - - // Attach to the child with PTRACE_SEIZE while setting PTRACE_O_TRACESYSGOOD. - ASSERT_THAT(ptrace(PTRACE_SEIZE, child_pid, 0, PTRACE_O_TRACESYSGOOD), - SyscallSucceeds()); - - // Stop the child with PTRACE_INTERRUPT. - ASSERT_THAT(ptrace(PTRACE_INTERRUPT, child_pid, 0, 0), SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_EQ(SIGTRAP | (kPtraceEventStop << 8), status >> 8); - - // Resume the child with PTRACE_SYSCALL and wait for it to enter - // syscall-enter-stop. The stop signal status from the syscall stop should be - // SIGTRAP|0x80, reflecting PTRACE_O_TRACESYSGOOD. - ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) - << " status " << status; - - // Clean up the child. - ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds()); - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) { - // "SIGKILL kills even within system calls (syscall-exit-stop is not - // generated prior to death by SIGKILL). The net effect is that SIGKILL - // always kills the process (all its threads), even if some threads of the - // process are ptraced." - ptrace(2). This is technically true, but... - // - // When we send SIGKILL to the child, kernel/signal.c:complete_signal() => - // signal_wake_up(resume=1) kicks the tracee out of the syscall-enter-stop. - // The pending SIGKILL causes the syscall to be skipped, but the child - // thread still reports syscall-exit before checking for pending signals; in - // current kernels, this is - // arch/x86/entry/common.c:syscall_return_slowpath() => - // syscall_slow_exit_work() => - // include/linux/tracehook.h:tracehook_report_syscall_exit() => - // ptrace_report_syscall() => kernel/signal.c:ptrace_notify() => - // ptrace_do_notify() => ptrace_stop(). - // - // ptrace_stop() sets the task's state to TASK_TRACED and the task's - // exit_code to SIGTRAP|0x80 (passed by ptrace_report_syscall()), then calls - // freezable_schedule(). freezable_schedule() eventually reaches - // __schedule(), which detects signal_pending_state() due to the pending - // SIGKILL, sets the task's state back to TASK_RUNNING, and returns without - // descheduling. Thus, the task never enters syscall-exit-stop. However, if - // our wait4() => kernel/exit.c:wait_task_stopped() racily observes the - // TASK_TRACED state and the non-zero exit code set by ptrace_stop() before - // __schedule() sets the state back to TASK_RUNNING, it will return the - // task's exit_code as status W_STOPCODE(SIGTRAP|0x80). So we get a spurious - // syscall-exit-stop notification, and need to wait4() again for task exit. - // - // gVisor is not susceptible to this race because - // kernel.Task.waitCollectTraceeStopLocked() checks specifically for an - // active ptraceStop, which is not initiated if SIGKILL is pending. - std::cout << "Observed syscall-exit after SIGKILL" << std::endl; - ASSERT_THAT(waitpid(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - } - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - << " status " << status; -} - -TEST(PtraceTest, SetYAMAPtraceScope) { - SKIP_IF(IsRunningWithVFS1()); - - // Do not modify the ptrace scope on the host. - SKIP_IF(!IsRunningOnGvisor()); - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(std::string(kYamaPtraceScopePath), O_RDWR)); - - ASSERT_THAT(write(fd.get(), "0", 1), SyscallSucceedsWithValue(1)); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds()); - std::vector<char> buf(10); - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), SyscallSucceeds()); - EXPECT_STREQ(buf.data(), "0\n"); - - // Test that a child can attach to its parent when ptrace_scope is 0. - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, false)); - pid_t const child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(CheckPtraceAttach(getppid()) == 0); - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - int status; - ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds()); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; - - // Set ptrace_scope back to 1 (and try writing with a newline). - ASSERT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds()); - ASSERT_THAT(write(fd.get(), "1\n", 2), SyscallSucceedsWithValue(2)); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds()); - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), SyscallSucceeds()); - EXPECT_STREQ(buf.data(), "1\n"); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_ptrace_test_execve_child)) { - gvisor::testing::RunExecveChild(); - } - - int fd = absl::GetFlag(FLAGS_ptrace_test_fd); - - if (absl::GetFlag(FLAGS_ptrace_test_trace_descendants_allowed)) { - gvisor::testing::RunTraceDescendantsAllowed(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_pid)) { - gvisor::testing::RunPrctlSetPtracerPID(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_any)) { - gvisor::testing::RunPrctlSetPtracerAny(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_clear_ptracer)) { - gvisor::testing::RunPrctlClearPtracer(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer)) { - gvisor::testing::RunPrctlReplacePtracer( - absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer_tid), fd); - } - - if (absl::GetFlag( - FLAGS_ptrace_test_prctl_set_ptracer_and_exit_tracee_thread)) { - gvisor::testing::RunPrctlSetPtracerPersistsPastTraceeThreadExit(fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_and_exec_non_leader)) { - gvisor::testing::RunPrctlSetPtracerDoesNotPersistPastNonLeaderExec( - fd); - } - - if (absl::GetFlag( - FLAGS_ptrace_test_prctl_set_ptracer_and_exit_tracer_thread)) { - gvisor::testing::RunPrctlSetPtracerDoesNotPersistPastTracerThreadExit( - absl::GetFlag( - FLAGS_ptrace_test_prctl_set_ptracer_and_exit_tracer_thread_tid), - fd); - } - - if (absl::GetFlag( - FLAGS_ptrace_test_prctl_set_ptracer_respects_tracer_thread_id)) { - gvisor::testing::RunPrctlSetPtracerRespectsTracerThreadID( - absl::GetFlag( - FLAGS_ptrace_test_prctl_set_ptracer_respects_tracer_thread_id_tid), - fd); - } - - if (absl::GetFlag(FLAGS_ptrace_test_tracee)) { - gvisor::testing::RunTracee(fd); - } - - int pid = absl::GetFlag(FLAGS_ptrace_test_trace_tid); - if (pid != -1) { - gvisor::testing::RunTraceTID(pid, fd); - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc deleted file mode 100644 index 8d15c491e..000000000 --- a/test/syscalls/linux/pty.cc +++ /dev/null @@ -1,1741 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/capability.h> -#include <linux/major.h> -#include <poll.h> -#include <sched.h> -#include <signal.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/sysmacros.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <termios.h> -#include <unistd.h> - -#include <iostream> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "absl/strings/str_cat.h" -#include "absl/synchronization/notification.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/pty_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::AnyOf; -using ::testing::Contains; -using ::testing::Eq; -using ::testing::Not; -using SubprocessCallback = std::function<void()>; - -// Tests Unix98 pseudoterminals. -// -// These tests assume that /dev/ptmx exists and is associated with a devpts -// filesystem mounted at /dev/pts/. While a Linux distribution could -// theoretically place those anywhere, glibc expects those locations, so they -// are effectively fixed. - -// Minor device number for an unopened ptmx file. -constexpr int kPtmxMinor = 2; - -// The timeout when polling for data from a pty. When data is written to one end -// of a pty, Linux asynchronously makes it available to the other end, so we -// have to wait. -constexpr absl::Duration kTimeout = absl::Seconds(20); - -// The maximum line size in bytes returned per read from a pty file. -constexpr int kMaxLineSize = 4096; - -constexpr char kMasterPath[] = "/dev/ptmx"; - -// glibc defines its own, different, version of struct termios. We care about -// what the kernel does, not glibc. -#define KERNEL_NCCS 19 -struct kernel_termios { - tcflag_t c_iflag; - tcflag_t c_oflag; - tcflag_t c_cflag; - tcflag_t c_lflag; - cc_t c_line; - cc_t c_cc[KERNEL_NCCS]; -}; - -bool operator==(struct kernel_termios const& a, - struct kernel_termios const& b) { - return memcmp(&a, &b, sizeof(a)) == 0; -} - -// Returns the termios-style control character for the passed character. -// -// e.g., for Ctrl-C, i.e., ^C, call ControlCharacter('C'). -// -// Standard control characters are ASCII bytes 0 through 31. -constexpr char ControlCharacter(char c) { - // A is 1, B is 2, etc. - return c - 'A' + 1; -} - -// Returns the printable character the given control character represents. -constexpr char FromControlCharacter(char c) { return c + 'A' - 1; } - -// Returns true if c is a control character. -// -// Standard control characters are ASCII bytes 0 through 31. -constexpr bool IsControlCharacter(char c) { return c <= 31; } - -struct Field { - const char* name; - uint64_t mask; - uint64_t value; -}; - -// ParseFields returns a string representation of value, using the names in -// fields. -std::string ParseFields(const Field* fields, size_t len, uint64_t value) { - bool first = true; - std::string s; - for (size_t i = 0; i < len; i++) { - const Field f = fields[i]; - if ((value & f.mask) == f.value) { - if (!first) { - s += "|"; - } - s += f.name; - first = false; - value &= ~f.mask; - } - } - - if (value) { - if (!first) { - s += "|"; - } - absl::StrAppend(&s, value); - } - - return s; -} - -const Field kIflagFields[] = { - {"IGNBRK", IGNBRK, IGNBRK}, {"BRKINT", BRKINT, BRKINT}, - {"IGNPAR", IGNPAR, IGNPAR}, {"PARMRK", PARMRK, PARMRK}, - {"INPCK", INPCK, INPCK}, {"ISTRIP", ISTRIP, ISTRIP}, - {"INLCR", INLCR, INLCR}, {"IGNCR", IGNCR, IGNCR}, - {"ICRNL", ICRNL, ICRNL}, {"IUCLC", IUCLC, IUCLC}, - {"IXON", IXON, IXON}, {"IXANY", IXANY, IXANY}, - {"IXOFF", IXOFF, IXOFF}, {"IMAXBEL", IMAXBEL, IMAXBEL}, - {"IUTF8", IUTF8, IUTF8}, -}; - -const Field kOflagFields[] = { - {"OPOST", OPOST, OPOST}, {"OLCUC", OLCUC, OLCUC}, - {"ONLCR", ONLCR, ONLCR}, {"OCRNL", OCRNL, OCRNL}, - {"ONOCR", ONOCR, ONOCR}, {"ONLRET", ONLRET, ONLRET}, - {"OFILL", OFILL, OFILL}, {"OFDEL", OFDEL, OFDEL}, - {"NL0", NLDLY, NL0}, {"NL1", NLDLY, NL1}, - {"CR0", CRDLY, CR0}, {"CR1", CRDLY, CR1}, - {"CR2", CRDLY, CR2}, {"CR3", CRDLY, CR3}, - {"TAB0", TABDLY, TAB0}, {"TAB1", TABDLY, TAB1}, - {"TAB2", TABDLY, TAB2}, {"TAB3", TABDLY, TAB3}, - {"BS0", BSDLY, BS0}, {"BS1", BSDLY, BS1}, - {"FF0", FFDLY, FF0}, {"FF1", FFDLY, FF1}, - {"VT0", VTDLY, VT0}, {"VT1", VTDLY, VT1}, - {"XTABS", XTABS, XTABS}, -}; - -#ifndef IBSHIFT -// Shift from CBAUD to CIBAUD. -#define IBSHIFT 16 -#endif - -const Field kCflagFields[] = { - {"B0", CBAUD, B0}, - {"B50", CBAUD, B50}, - {"B75", CBAUD, B75}, - {"B110", CBAUD, B110}, - {"B134", CBAUD, B134}, - {"B150", CBAUD, B150}, - {"B200", CBAUD, B200}, - {"B300", CBAUD, B300}, - {"B600", CBAUD, B600}, - {"B1200", CBAUD, B1200}, - {"B1800", CBAUD, B1800}, - {"B2400", CBAUD, B2400}, - {"B4800", CBAUD, B4800}, - {"B9600", CBAUD, B9600}, - {"B19200", CBAUD, B19200}, - {"B38400", CBAUD, B38400}, - {"CS5", CSIZE, CS5}, - {"CS6", CSIZE, CS6}, - {"CS7", CSIZE, CS7}, - {"CS8", CSIZE, CS8}, - {"CSTOPB", CSTOPB, CSTOPB}, - {"CREAD", CREAD, CREAD}, - {"PARENB", PARENB, PARENB}, - {"PARODD", PARODD, PARODD}, - {"HUPCL", HUPCL, HUPCL}, - {"CLOCAL", CLOCAL, CLOCAL}, - {"B57600", CBAUD, B57600}, - {"B115200", CBAUD, B115200}, - {"B230400", CBAUD, B230400}, - {"B460800", CBAUD, B460800}, - {"B500000", CBAUD, B500000}, - {"B576000", CBAUD, B576000}, - {"B921600", CBAUD, B921600}, - {"B1000000", CBAUD, B1000000}, - {"B1152000", CBAUD, B1152000}, - {"B1500000", CBAUD, B1500000}, - {"B2000000", CBAUD, B2000000}, - {"B2500000", CBAUD, B2500000}, - {"B3000000", CBAUD, B3000000}, - {"B3500000", CBAUD, B3500000}, - {"B4000000", CBAUD, B4000000}, - {"CMSPAR", CMSPAR, CMSPAR}, - {"CRTSCTS", CRTSCTS, CRTSCTS}, - {"IB0", CIBAUD, B0 << IBSHIFT}, - {"IB50", CIBAUD, B50 << IBSHIFT}, - {"IB75", CIBAUD, B75 << IBSHIFT}, - {"IB110", CIBAUD, B110 << IBSHIFT}, - {"IB134", CIBAUD, B134 << IBSHIFT}, - {"IB150", CIBAUD, B150 << IBSHIFT}, - {"IB200", CIBAUD, B200 << IBSHIFT}, - {"IB300", CIBAUD, B300 << IBSHIFT}, - {"IB600", CIBAUD, B600 << IBSHIFT}, - {"IB1200", CIBAUD, B1200 << IBSHIFT}, - {"IB1800", CIBAUD, B1800 << IBSHIFT}, - {"IB2400", CIBAUD, B2400 << IBSHIFT}, - {"IB4800", CIBAUD, B4800 << IBSHIFT}, - {"IB9600", CIBAUD, B9600 << IBSHIFT}, - {"IB19200", CIBAUD, B19200 << IBSHIFT}, - {"IB38400", CIBAUD, B38400 << IBSHIFT}, - {"IB57600", CIBAUD, B57600 << IBSHIFT}, - {"IB115200", CIBAUD, B115200 << IBSHIFT}, - {"IB230400", CIBAUD, B230400 << IBSHIFT}, - {"IB460800", CIBAUD, B460800 << IBSHIFT}, - {"IB500000", CIBAUD, B500000 << IBSHIFT}, - {"IB576000", CIBAUD, B576000 << IBSHIFT}, - {"IB921600", CIBAUD, B921600 << IBSHIFT}, - {"IB1000000", CIBAUD, B1000000 << IBSHIFT}, - {"IB1152000", CIBAUD, B1152000 << IBSHIFT}, - {"IB1500000", CIBAUD, B1500000 << IBSHIFT}, - {"IB2000000", CIBAUD, B2000000 << IBSHIFT}, - {"IB2500000", CIBAUD, B2500000 << IBSHIFT}, - {"IB3000000", CIBAUD, B3000000 << IBSHIFT}, - {"IB3500000", CIBAUD, B3500000 << IBSHIFT}, - {"IB4000000", CIBAUD, B4000000 << IBSHIFT}, -}; - -const Field kLflagFields[] = { - {"ISIG", ISIG, ISIG}, {"ICANON", ICANON, ICANON}, - {"XCASE", XCASE, XCASE}, {"ECHO", ECHO, ECHO}, - {"ECHOE", ECHOE, ECHOE}, {"ECHOK", ECHOK, ECHOK}, - {"ECHONL", ECHONL, ECHONL}, {"NOFLSH", NOFLSH, NOFLSH}, - {"TOSTOP", TOSTOP, TOSTOP}, {"ECHOCTL", ECHOCTL, ECHOCTL}, - {"ECHOPRT", ECHOPRT, ECHOPRT}, {"ECHOKE", ECHOKE, ECHOKE}, - {"FLUSHO", FLUSHO, FLUSHO}, {"PENDIN", PENDIN, PENDIN}, - {"IEXTEN", IEXTEN, IEXTEN}, {"EXTPROC", EXTPROC, EXTPROC}, -}; - -std::string FormatCC(char c) { - if (isgraph(c)) { - return std::string(1, c); - } else if (c == ' ') { - return " "; - } else if (c == '\t') { - return "\\t"; - } else if (c == '\r') { - return "\\r"; - } else if (c == '\n') { - return "\\n"; - } else if (c == '\0') { - return "\\0"; - } else if (IsControlCharacter(c)) { - return absl::StrCat("^", std::string(1, FromControlCharacter(c))); - } - return absl::StrCat("\\x", absl::Hex(c)); -} - -std::ostream& operator<<(std::ostream& os, struct kernel_termios const& a) { - os << "{ c_iflag = " - << ParseFields(kIflagFields, ABSL_ARRAYSIZE(kIflagFields), a.c_iflag); - os << ", c_oflag = " - << ParseFields(kOflagFields, ABSL_ARRAYSIZE(kOflagFields), a.c_oflag); - os << ", c_cflag = " - << ParseFields(kCflagFields, ABSL_ARRAYSIZE(kCflagFields), a.c_cflag); - os << ", c_lflag = " - << ParseFields(kLflagFields, ABSL_ARRAYSIZE(kLflagFields), a.c_lflag); - os << ", c_line = " << a.c_line; - os << ", c_cc = { [VINTR] = '" << FormatCC(a.c_cc[VINTR]); - os << "', [VQUIT] = '" << FormatCC(a.c_cc[VQUIT]); - os << "', [VERASE] = '" << FormatCC(a.c_cc[VERASE]); - os << "', [VKILL] = '" << FormatCC(a.c_cc[VKILL]); - os << "', [VEOF] = '" << FormatCC(a.c_cc[VEOF]); - os << "', [VTIME] = '" << static_cast<int>(a.c_cc[VTIME]); - os << "', [VMIN] = " << static_cast<int>(a.c_cc[VMIN]); - os << ", [VSWTC] = '" << FormatCC(a.c_cc[VSWTC]); - os << "', [VSTART] = '" << FormatCC(a.c_cc[VSTART]); - os << "', [VSTOP] = '" << FormatCC(a.c_cc[VSTOP]); - os << "', [VSUSP] = '" << FormatCC(a.c_cc[VSUSP]); - os << "', [VEOL] = '" << FormatCC(a.c_cc[VEOL]); - os << "', [VREPRINT] = '" << FormatCC(a.c_cc[VREPRINT]); - os << "', [VDISCARD] = '" << FormatCC(a.c_cc[VDISCARD]); - os << "', [VWERASE] = '" << FormatCC(a.c_cc[VWERASE]); - os << "', [VLNEXT] = '" << FormatCC(a.c_cc[VLNEXT]); - os << "', [VEOL2] = '" << FormatCC(a.c_cc[VEOL2]); - os << "'}"; - return os; -} - -// Return the default termios settings for a new terminal. -struct kernel_termios DefaultTermios() { - struct kernel_termios t = {}; - t.c_iflag = IXON | ICRNL; - t.c_oflag = OPOST | ONLCR; - t.c_cflag = B38400 | CSIZE | CS8 | CREAD; - t.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; - t.c_line = 0; - t.c_cc[VINTR] = ControlCharacter('C'); - t.c_cc[VQUIT] = ControlCharacter('\\'); - t.c_cc[VERASE] = '\x7f'; - t.c_cc[VKILL] = ControlCharacter('U'); - t.c_cc[VEOF] = ControlCharacter('D'); - t.c_cc[VTIME] = '\0'; - t.c_cc[VMIN] = 1; - t.c_cc[VSWTC] = '\0'; - t.c_cc[VSTART] = ControlCharacter('Q'); - t.c_cc[VSTOP] = ControlCharacter('S'); - t.c_cc[VSUSP] = ControlCharacter('Z'); - t.c_cc[VEOL] = '\0'; - t.c_cc[VREPRINT] = ControlCharacter('R'); - t.c_cc[VDISCARD] = ControlCharacter('O'); - t.c_cc[VWERASE] = ControlCharacter('W'); - t.c_cc[VLNEXT] = ControlCharacter('V'); - t.c_cc[VEOL2] = '\0'; - return t; -} - -// PollAndReadFd tries to read count bytes from buf within timeout. -// -// Returns a partial read if some bytes were read. -// -// fd must be non-blocking. -PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count, - absl::Duration timeout) { - absl::Time end = absl::Now() + timeout; - - size_t completed = 0; - absl::Duration remaining; - while ((remaining = end - absl::Now()) > absl::ZeroDuration()) { - struct pollfd pfd = {fd, POLLIN, 0}; - int ret = RetryEINTR(poll)(&pfd, 1, absl::ToInt64Milliseconds(remaining)); - if (ret < 0) { - return PosixError(errno, "poll failed"); - } else if (ret == 0) { - // Timed out. - continue; - } else if (ret != 1) { - return PosixError(EINVAL, absl::StrCat("Bad poll ret ", ret)); - } - - ssize_t n = - ReadFd(fd, static_cast<char*>(buf) + completed, count - completed); - if (n < 0) { - if (errno == EAGAIN) { - // Linux sometimes returns EAGAIN from this read, despite the fact that - // poll returned success. Let's just do what do as we are told and try - // again. - continue; - } - return PosixError(errno, "read failed"); - } - completed += n; - if (completed >= count) { - return completed; - } - } - - if (completed) { - return completed; - } - return PosixError(ETIMEDOUT, "Poll timed out"); -} - -TEST(PtyTrunc, Truncate) { - // Opening PTYs with O_TRUNC shouldn't cause an error, but calls to - // (f)truncate should. - FileDescriptor master = - ASSERT_NO_ERRNO_AND_VALUE(Open(kMasterPath, O_RDWR | O_TRUNC)); - int n = ASSERT_NO_ERRNO_AND_VALUE(ReplicaID(master)); - std::string spath = absl::StrCat("/dev/pts/", n); - FileDescriptor replica = - ASSERT_NO_ERRNO_AND_VALUE(Open(spath, O_RDWR | O_NONBLOCK | O_TRUNC)); - - EXPECT_THAT(truncate(kMasterPath, 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(truncate(spath.c_str(), 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(ftruncate(master.get(), 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(ftruncate(replica.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(BasicPtyTest, StatUnopenedMaster) { - struct stat s; - ASSERT_THAT(stat(kMasterPath, &s), SyscallSucceeds()); - - EXPECT_EQ(s.st_rdev, makedev(TTYAUX_MAJOR, kPtmxMinor)); - EXPECT_EQ(s.st_size, 0); - EXPECT_EQ(s.st_blocks, 0); - - // ptmx attached to a specific devpts mount uses block size 1024. See - // fs/devpts/inode.c:devpts_fill_super. - // - // The global ptmx device uses the block size of the filesystem it is created - // on (which is usually 4096 for disk filesystems). - EXPECT_THAT(s.st_blksize, AnyOf(Eq(1024), Eq(4096))); -} - -// Waits for count bytes to be readable from fd. Unlike poll, which can return -// before all data is moved into a pty's read buffer, this function waits for -// all count bytes to become readable. -PosixErrorOr<int> WaitUntilReceived(int fd, int count) { - int buffered = -1; - absl::Duration remaining; - absl::Time end = absl::Now() + kTimeout; - while ((remaining = end - absl::Now()) > absl::ZeroDuration()) { - if (ioctl(fd, FIONREAD, &buffered) < 0) { - return PosixError(errno, "failed FIONREAD ioctl"); - } - if (buffered >= count) { - return buffered; - } - absl::SleepFor(absl::Milliseconds(500)); - } - return PosixError( - ETIMEDOUT, - absl::StrFormat( - "FIONREAD timed out, receiving only %d of %d expected bytes", - buffered, count)); -} - -// Verifies that there is nothing left to read from fd. -void ExpectFinished(const FileDescriptor& fd) { - // Nothing more to read. - char c; - EXPECT_THAT(ReadFd(fd.get(), &c, 1), SyscallFailsWithErrno(EAGAIN)); -} - -// Verifies that we can read expected bytes from fd into buf. -void ExpectReadable(const FileDescriptor& fd, int expected, char* buf) { - size_t n = ASSERT_NO_ERRNO_AND_VALUE( - PollAndReadFd(fd.get(), buf, expected, kTimeout)); - EXPECT_EQ(expected, n); -} - -TEST(BasicPtyTest, OpenMasterReplica) { - FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); -} - -TEST(BasicPtyTest, OpenSetsControllingTTY) { - SKIP_IF(IsRunningWithVFS1()); - // setsid either puts us in a new session or fails because we're already the - // session leader. Either way, this ensures we're the session leader. - setsid(); - - // Make sure we're ignoring SIGHUP, which will be sent to this process once we - // disconnect they TTY. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - struct sigaction old_sa; - ASSERT_THAT(sigaction(SIGHUP, &sa, &old_sa), SyscallSucceeds()); - auto cleanup = Cleanup([old_sa] { - EXPECT_THAT(sigaction(SIGHUP, &old_sa, NULL), SyscallSucceeds()); - }); - - FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = - ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master, O_NONBLOCK | O_RDWR)); - - // Opening replica should make it our controlling TTY, and therefore we are - // able to give it up. - ASSERT_THAT(ioctl(replica.get(), TIOCNOTTY), SyscallSucceeds()); -} - -TEST(BasicPtyTest, OpenMasterDoesNotSetsControllingTTY) { - SKIP_IF(IsRunningWithVFS1()); - // setsid either puts us in a new session or fails because we're already the - // session leader. Either way, this ensures we're the session leader. - setsid(); - FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - - // Opening master does not set the controlling TTY, and therefore we are - // unable to give it up. - ASSERT_THAT(ioctl(master.get(), TIOCNOTTY), SyscallFailsWithErrno(ENOTTY)); -} - -TEST(BasicPtyTest, OpenNOCTTY) { - SKIP_IF(IsRunningWithVFS1()); - // setsid either puts us in a new session or fails because we're already the - // session leader. Either way, this ensures we're the session leader. - setsid(); - FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE( - OpenReplica(master, O_NOCTTY | O_NONBLOCK | O_RDWR)); - - // Opening replica with O_NOCTTY won't make it our controlling TTY, and - // therefore we are unable to give it up. - ASSERT_THAT(ioctl(replica.get(), TIOCNOTTY), SyscallFailsWithErrno(ENOTTY)); -} - -// The replica entry in /dev/pts/ disappears when the master is closed, even if -// the replica is still open. -TEST(BasicPtyTest, ReplicaEntryGoneAfterMasterClose) { - FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); - - // Get pty index. - int index = -1; - ASSERT_THAT(ioctl(master.get(), TIOCGPTN, &index), SyscallSucceeds()); - - std::string path = absl::StrCat("/dev/pts/", index); - - struct stat st; - EXPECT_THAT(stat(path.c_str(), &st), SyscallSucceeds()); - - master.reset(); - - EXPECT_THAT(stat(path.c_str(), &st), SyscallFailsWithErrno(ENOENT)); -} - -TEST(BasicPtyTest, Getdents) { - FileDescriptor master1 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - int index1 = -1; - ASSERT_THAT(ioctl(master1.get(), TIOCGPTN, &index1), SyscallSucceeds()); - FileDescriptor replica1 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master1)); - - FileDescriptor master2 = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR)); - int index2 = -1; - ASSERT_THAT(ioctl(master2.get(), TIOCGPTN, &index2), SyscallSucceeds()); - FileDescriptor replica2 = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master2)); - - // The directory contains ptmx, index1, and index2. (Plus any additional PTYs - // unrelated to this test.) - - std::vector<std::string> contents = - ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev/pts/", true)); - EXPECT_THAT(contents, Contains(absl::StrCat(index1))); - EXPECT_THAT(contents, Contains(absl::StrCat(index2))); - - master2.reset(); - - // The directory contains ptmx and index1, but not index2 since the master is - // closed. (Plus any additional PTYs unrelated to this test.) - - contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/dev/pts/", true)); - EXPECT_THAT(contents, Contains(absl::StrCat(index1))); - EXPECT_THAT(contents, Not(Contains(absl::StrCat(index2)))); - - // N.B. devpts supports legacy "single-instance" mode and new "multi-instance" - // mode. In legacy mode, devpts does not contain a "ptmx" device (the distro - // must use mknod to create it somewhere, presumably /dev/ptmx). - // Multi-instance mode does include a "ptmx" device tied to that mount. - // - // We don't check for the presence or absence of "ptmx", as distros vary in - // their usage of the two modes. -} - -class PtyTest : public ::testing::Test { - protected: - void SetUp() override { - master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master_)); - } - - void DisableCanonical() { - struct kernel_termios t = {}; - EXPECT_THAT(ioctl(replica_.get(), TCGETS, &t), SyscallSucceeds()); - t.c_lflag &= ~ICANON; - EXPECT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - } - - void EnableCanonical() { - struct kernel_termios t = {}; - EXPECT_THAT(ioctl(replica_.get(), TCGETS, &t), SyscallSucceeds()); - t.c_lflag |= ICANON; - EXPECT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - } - - // Master and replica ends of the PTY. Non-blocking. - FileDescriptor master_; - FileDescriptor replica_; -}; - -// Master to replica sanity test. -TEST_F(PtyTest, WriteMasterToReplica) { - // N.B. by default, the replica reads nothing until the master writes a - // newline. - constexpr char kBuf[] = "hello\n"; - - EXPECT_THAT(WriteFd(master_.get(), kBuf, sizeof(kBuf) - 1), - SyscallSucceedsWithValue(sizeof(kBuf) - 1)); - - // Linux moves data from the master to the replica via async work scheduled - // via tty_flip_buffer_push. Since it is asynchronous, the data may not be - // available for reading immediately. Instead we must poll and assert that it - // becomes available "soon". - - char buf[sizeof(kBuf)] = {}; - ExpectReadable(replica_, sizeof(buf) - 1, buf); - - EXPECT_EQ(memcmp(buf, kBuf, sizeof(kBuf)), 0); -} - -// Replica to master sanity test. -TEST_F(PtyTest, WriteReplicaToMaster) { - // N.B. by default, the master reads nothing until the replica writes a - // newline, and the master gets a carriage return. - constexpr char kInput[] = "hello\n"; - constexpr char kExpected[] = "hello\r\n"; - - EXPECT_THAT(WriteFd(replica_.get(), kInput, sizeof(kInput) - 1), - SyscallSucceedsWithValue(sizeof(kInput) - 1)); - - // Linux moves data from the master to the replica via async work scheduled - // via tty_flip_buffer_push. Since it is asynchronous, the data may not be - // available for reading immediately. Instead we must poll and assert that it - // becomes available "soon". - - char buf[sizeof(kExpected)] = {}; - ExpectReadable(master_, sizeof(buf) - 1, buf); - - EXPECT_EQ(memcmp(buf, kExpected, sizeof(kExpected)), 0); -} - -TEST_F(PtyTest, WriteInvalidUTF8) { - char c = 0xff; - ASSERT_THAT(syscall(__NR_write, master_.get(), &c, sizeof(c)), - SyscallSucceedsWithValue(sizeof(c))); -} - -// Both the master and replica report the standard default termios settings. -// -// Note that TCGETS on the master actually redirects to the replica (see comment -// on MasterTermiosUnchangable). -TEST_F(PtyTest, DefaultTermios) { - struct kernel_termios t = {}; - EXPECT_THAT(ioctl(replica_.get(), TCGETS, &t), SyscallSucceeds()); - EXPECT_EQ(t, DefaultTermios()); - - EXPECT_THAT(ioctl(master_.get(), TCGETS, &t), SyscallSucceeds()); - EXPECT_EQ(t, DefaultTermios()); -} - -// Changing termios from the master actually affects the replica. -// -// TCSETS on the master actually redirects to the replica (see comment on -// MasterTermiosUnchangable). -TEST_F(PtyTest, TermiosAffectsReplica) { - struct kernel_termios master_termios = {}; - EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds()); - master_termios.c_lflag ^= ICANON; - EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds()); - - struct kernel_termios replica_termios = {}; - EXPECT_THAT(ioctl(replica_.get(), TCGETS, &replica_termios), - SyscallSucceeds()); - EXPECT_EQ(master_termios, replica_termios); -} - -// The master end of the pty has termios: -// -// struct kernel_termios t = { -// .c_iflag = 0; -// .c_oflag = 0; -// .c_cflag = B38400 | CS8 | CREAD; -// .c_lflag = 0; -// .c_cc = /* same as DefaultTermios */ -// } -// -// (From drivers/tty/pty.c:unix98_pty_init) -// -// All termios control ioctls on the master actually redirect to the replica -// (drivers/tty/tty_ioctl.c:tty_mode_ioctl), making it impossible to change the -// master termios. -// -// Verify this by setting ICRNL (which rewrites input \r to \n) and verify that -// it has no effect on the master. -TEST_F(PtyTest, MasterTermiosUnchangable) { - struct kernel_termios master_termios = {}; - EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds()); - master_termios.c_lflag |= ICRNL; - EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds()); - - char c = '\r'; - ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - ExpectReadable(master_, 1, &c); - EXPECT_EQ(c, '\r'); // ICRNL had no effect! - - ExpectFinished(master_); -} - -// ICRNL rewrites input \r to \n. -TEST_F(PtyTest, TermiosICRNL) { - struct kernel_termios t = DefaultTermios(); - t.c_iflag |= ICRNL; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - char c = '\r'; - ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - ExpectReadable(replica_, 1, &c); - EXPECT_EQ(c, '\n'); - - ExpectFinished(replica_); -} - -// ONLCR rewrites output \n to \r\n. -TEST_F(PtyTest, TermiosONLCR) { - struct kernel_termios t = DefaultTermios(); - t.c_oflag |= ONLCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - char c = '\n'; - ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - // Extra byte for NUL for EXPECT_STREQ. - char buf[3] = {}; - ExpectReadable(master_, 2, buf); - EXPECT_STREQ(buf, "\r\n"); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, TermiosIGNCR) { - struct kernel_termios t = DefaultTermios(); - t.c_iflag |= IGNCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - char c = '\r'; - ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - // Nothing to read. - ASSERT_THAT(PollAndReadFd(replica_.get(), &c, 1, kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); -} - -// Test that we can successfully poll for readable data from the replica. -TEST_F(PtyTest, TermiosPollReplica) { - struct kernel_termios t = DefaultTermios(); - t.c_iflag |= IGNCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - absl::Notification notify; - int sfd = replica_.get(); - ScopedThread th([sfd, ¬ify]() { - notify.Notify(); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {sfd, POLLIN, 0}; - EXPECT_THAT( - RetryEINTR(poll)(&poll_fd, 1, absl::ToInt64Milliseconds(kTimeout)), - SyscallSucceedsWithValue(1)); - - // Should trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN); - }); - - notify.WaitForNotification(); - // Sleep ensures that poll begins waiting before we write to the FD. - absl::SleepFor(absl::Seconds(1)); - - char s[] = "foo\n"; - ASSERT_THAT(WriteFd(master_.get(), s, strlen(s) + 1), SyscallSucceeds()); -} - -// Test that we can successfully poll for readable data from the master. -TEST_F(PtyTest, TermiosPollMaster) { - struct kernel_termios t = DefaultTermios(); - t.c_iflag |= IGNCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(master_.get(), TCSETS, &t), SyscallSucceeds()); - - absl::Notification notify; - int mfd = master_.get(); - ScopedThread th([mfd, ¬ify]() { - notify.Notify(); - - // Poll on the reader fd with POLLIN event. - struct pollfd poll_fd = {mfd, POLLIN, 0}; - EXPECT_THAT( - RetryEINTR(poll)(&poll_fd, 1, absl::ToInt64Milliseconds(kTimeout)), - SyscallSucceedsWithValue(1)); - - // Should trigger POLLIN event. - EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN); - }); - - notify.WaitForNotification(); - // Sleep ensures that poll begins waiting before we write to the FD. - absl::SleepFor(absl::Seconds(1)); - - char s[] = "foo\n"; - ASSERT_THAT(WriteFd(replica_.get(), s, strlen(s) + 1), SyscallSucceeds()); -} - -TEST_F(PtyTest, TermiosINLCR) { - struct kernel_termios t = DefaultTermios(); - t.c_iflag |= INLCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - char c = '\n'; - ASSERT_THAT(WriteFd(master_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - ExpectReadable(replica_, 1, &c); - EXPECT_EQ(c, '\r'); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, TermiosONOCR) { - struct kernel_termios t = DefaultTermios(); - t.c_oflag |= ONOCR; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - // The terminal is at column 0, so there should be no CR to read. - char c = '\r'; - ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - // Nothing to read. - ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); - - // This time the column is greater than 0, so we should be able to read the CR - // out of the other end. - constexpr char kInput[] = "foo\r"; - constexpr int kInputSize = sizeof(kInput) - 1; - ASSERT_THAT(WriteFd(replica_.get(), kInput, kInputSize), - SyscallSucceedsWithValue(kInputSize)); - - char buf[kInputSize] = {}; - ExpectReadable(master_, kInputSize, buf); - - EXPECT_EQ(memcmp(buf, kInput, kInputSize), 0); - - ExpectFinished(master_); - - // Terminal should be at column 0 again, so no CR can be read. - ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - // Nothing to read. - ASSERT_THAT(PollAndReadFd(master_.get(), &c, 1, kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); -} - -TEST_F(PtyTest, TermiosOCRNL) { - struct kernel_termios t = DefaultTermios(); - t.c_oflag |= OCRNL; - t.c_lflag &= ~ICANON; // for byte-by-byte reading. - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - - // The terminal is at column 0, so there should be no CR to read. - char c = '\r'; - ASSERT_THAT(WriteFd(replica_.get(), &c, 1), SyscallSucceedsWithValue(1)); - - ExpectReadable(master_, 1, &c); - EXPECT_EQ(c, '\n'); - - ExpectFinished(master_); -} - -// Tests that VEOL is disabled when we start, and that we can set it to enable -// it. -TEST_F(PtyTest, VEOLTermination) { - // Write a few bytes ending with '\0', and confirm that we can't read. - constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - char buf[sizeof(kInput)] = {}; - ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(kInput), kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); - - // Set the EOL character to '=' and write it. - constexpr char delim = '='; - struct kernel_termios t = DefaultTermios(); - t.c_cc[VEOL] = delim; - ASSERT_THAT(ioctl(replica_.get(), TCSETS, &t), SyscallSucceeds()); - ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); - - // Now we can read, as sending EOL caused the line to become available. - ExpectReadable(replica_, sizeof(kInput), buf); - EXPECT_EQ(memcmp(buf, kInput, sizeof(kInput)), 0); - - ExpectReadable(replica_, 1, buf); - EXPECT_EQ(buf[0], '='); - - ExpectFinished(replica_); -} - -// Tests that we can write more than the 4096 character limit, then a -// terminating character, then read out just the first 4095 bytes plus the -// terminator. -TEST_F(PtyTest, CanonBigWrite) { - constexpr int kWriteLen = kMaxLineSize + 4; - char input[kWriteLen]; - memset(input, 'M', kWriteLen - 1); - input[kWriteLen - 1] = '\n'; - ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), - SyscallSucceedsWithValue(kWriteLen)); - - // We can read the line. - char buf[kMaxLineSize] = {}; - ExpectReadable(replica_, kMaxLineSize, buf); - - ExpectFinished(replica_); -} - -// Tests that data written in canonical mode can be read immediately once -// switched to noncanonical mode. -TEST_F(PtyTest, SwitchCanonToNoncanon) { - // Write a few bytes without a terminating character, switch to noncanonical - // mode, and read them. - constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - - // Nothing available yet. - char buf[sizeof(kInput)] = {}; - ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(kInput), kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); - - DisableCanonical(); - - ExpectReadable(replica_, sizeof(kInput), buf); - EXPECT_STREQ(buf, kInput); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchCanonToNonCanonNewline) { - // Write a few bytes with a terminating character. - constexpr char kInput[] = "hello\n"; - ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - - DisableCanonical(); - - // We can read the line. - char buf[sizeof(kInput)] = {}; - ExpectReadable(replica_, sizeof(kInput), buf); - EXPECT_STREQ(buf, kInput); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchNoncanonToCanonNewlineBig) { - DisableCanonical(); - - // Write more than the maximum line size, then write a delimiter. - constexpr int kWriteLen = 4100; - char input[kWriteLen]; - memset(input, 'M', kWriteLen); - ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), - SyscallSucceedsWithValue(kWriteLen)); - // Wait for the input queue to fill. - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); - constexpr char delim = '\n'; - ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); - - EnableCanonical(); - - // We can read the line. - char buf[kMaxLineSize] = {}; - ExpectReadable(replica_, kMaxLineSize - 1, buf); - - // We can also read the remaining characters. - ExpectReadable(replica_, 6, buf); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchNoncanonToCanonNoNewline) { - DisableCanonical(); - - // Write a few bytes without a terminating character. - // mode, and read them. - constexpr char kInput[] = "hello"; - ASSERT_THAT(WriteFd(master_.get(), kInput, sizeof(kInput) - 1), - SyscallSucceedsWithValue(sizeof(kInput) - 1)); - - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(kInput) - 1)); - EnableCanonical(); - - // We can read the line. - char buf[sizeof(kInput)] = {}; - ExpectReadable(replica_, sizeof(kInput) - 1, buf); - EXPECT_STREQ(buf, kInput); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchNoncanonToCanonNoNewlineBig) { - DisableCanonical(); - - // Write a few bytes without a terminating character. - // mode, and read them. - constexpr int kWriteLen = 4100; - char input[kWriteLen]; - memset(input, 'M', kWriteLen); - ASSERT_THAT(WriteFd(master_.get(), input, kWriteLen), - SyscallSucceedsWithValue(kWriteLen)); - - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); - EnableCanonical(); - - // We can read the line. - char buf[kMaxLineSize] = {}; - ExpectReadable(replica_, kMaxLineSize - 1, buf); - - ExpectFinished(replica_); -} - -// Tests that we can write over the 4095 noncanonical limit, then read out -// everything. -TEST_F(PtyTest, NoncanonBigWrite) { - DisableCanonical(); - - // Write well over the 4095 internal buffer limit. - constexpr char kInput = 'M'; - constexpr int kInputSize = kMaxLineSize * 2; - for (int i = 0; i < kInputSize; i++) { - // This makes too many syscalls for save/restore. - const DisableSave ds; - ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - } - - // We should be able to read out everything. Sleep a bit so that Linux has a - // chance to move data from the master to the replica. - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kMaxLineSize - 1)); - for (int i = 0; i < kInputSize; i++) { - // This makes too many syscalls for save/restore. - const DisableSave ds; - char c; - ExpectReadable(replica_, 1, &c); - ASSERT_EQ(c, kInput); - } - - ExpectFinished(replica_); -} - -// ICANON doesn't make input available until a line delimiter is typed. -// -// Test newline. -TEST_F(PtyTest, TermiosICANONNewline) { - char input[3] = {'a', 'b', 'c'}; - ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)), - SyscallSucceedsWithValue(sizeof(input))); - - // Extra bytes for newline (written later) and NUL for EXPECT_STREQ. - char buf[5] = {}; - - // Nothing available yet. - ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(input), kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); - - char delim = '\n'; - ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); - - // Now it is available. - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(input) + 1)); - ExpectReadable(replica_, sizeof(input) + 1, buf); - EXPECT_STREQ(buf, "abc\n"); - - ExpectFinished(replica_); -} - -// ICANON doesn't make input available until a line delimiter is typed. -// -// Test EOF (^D). -TEST_F(PtyTest, TermiosICANONEOF) { - char input[3] = {'a', 'b', 'c'}; - ASSERT_THAT(WriteFd(master_.get(), input, sizeof(input)), - SyscallSucceedsWithValue(sizeof(input))); - - // Extra byte for NUL for EXPECT_STREQ. - char buf[4] = {}; - - // Nothing available yet. - ASSERT_THAT(PollAndReadFd(replica_.get(), buf, sizeof(input), kTimeout), - PosixErrorIs(ETIMEDOUT, ::testing::StrEq("Poll timed out"))); - char delim = ControlCharacter('D'); - ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); - - // Now it is available. Note that ^D is not included. - ExpectReadable(replica_, sizeof(input), buf); - EXPECT_STREQ(buf, "abc"); - - ExpectFinished(replica_); -} - -// ICANON limits us to 4096 bytes including a terminating character. Anything -// after and 4095th character is discarded (although still processed for -// signals and echoing). -TEST_F(PtyTest, CanonDiscard) { - constexpr char kInput = 'M'; - constexpr int kInputSize = 4100; - constexpr int kIter = 3; - - // A few times write more than the 4096 character maximum, then a newline. - constexpr char delim = '\n'; - for (int i = 0; i < kIter; i++) { - // This makes too many syscalls for save/restore. - const DisableSave ds; - for (int i = 0; i < kInputSize; i++) { - ASSERT_THAT(WriteFd(master_.get(), &kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - } - ASSERT_THAT(WriteFd(master_.get(), &delim, 1), SyscallSucceedsWithValue(1)); - } - - // There should be multiple truncated lines available to read. - for (int i = 0; i < kIter; i++) { - char buf[kInputSize] = {}; - ExpectReadable(replica_, kMaxLineSize, buf); - EXPECT_EQ(buf[kMaxLineSize - 1], delim); - EXPECT_EQ(buf[kMaxLineSize - 2], kInput); - } - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, CanonMultiline) { - constexpr char kInput1[] = "GO\n"; - constexpr char kInput2[] = "BLUE\n"; - - // Write both lines. - ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), - SyscallSucceedsWithValue(sizeof(kInput1) - 1)); - ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1), - SyscallSucceedsWithValue(sizeof(kInput2) - 1)); - - // Get the first line. - char line1[8] = {}; - ExpectReadable(replica_, sizeof(kInput1) - 1, line1); - EXPECT_STREQ(line1, kInput1); - - // Get the second line. - char line2[8] = {}; - ExpectReadable(replica_, sizeof(kInput2) - 1, line2); - EXPECT_STREQ(line2, kInput2); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchNoncanonToCanonMultiline) { - DisableCanonical(); - - constexpr char kInput1[] = "GO\n"; - constexpr char kInput2[] = "BLUE\n"; - constexpr char kExpected[] = "GO\nBLUE\n"; - - // Write both lines. - ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), - SyscallSucceedsWithValue(sizeof(kInput1) - 1)); - ASSERT_THAT(WriteFd(master_.get(), kInput2, sizeof(kInput2) - 1), - SyscallSucceedsWithValue(sizeof(kInput2) - 1)); - - ASSERT_NO_ERRNO( - WaitUntilReceived(replica_.get(), sizeof(kInput1) + sizeof(kInput2) - 2)); - EnableCanonical(); - - // Get all together as one line. - char line[9] = {}; - ExpectReadable(replica_, 8, line); - EXPECT_STREQ(line, kExpected); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, SwitchTwiceMultiline) { - std::string kInputs[] = {"GO\n", "BLUE\n", "!"}; - std::string kExpected = "GO\nBLUE\n!"; - - // Write each line. - for (const std::string& input : kInputs) { - ASSERT_THAT(WriteFd(master_.get(), input.c_str(), input.size()), - SyscallSucceedsWithValue(input.size())); - } - - DisableCanonical(); - // All written characters have to make it into the input queue before - // canonical mode is re-enabled. If the final '!' character hasn't been - // enqueued before canonical mode is re-enabled, it won't be readable. - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), kExpected.size())); - EnableCanonical(); - - // Get all together as one line. - char line[10] = {}; - ExpectReadable(replica_, 9, line); - EXPECT_STREQ(line, kExpected.c_str()); - - ExpectFinished(replica_); -} - -TEST_F(PtyTest, QueueSize) { - // Write the line. - constexpr char kInput1[] = "GO\n"; - ASSERT_THAT(WriteFd(master_.get(), kInput1, sizeof(kInput1) - 1), - SyscallSucceedsWithValue(sizeof(kInput1) - 1)); - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), sizeof(kInput1) - 1)); - - // Ensure that writing more (beyond what is readable) does not impact the - // readable size. - char input[kMaxLineSize]; - memset(input, 'M', kMaxLineSize); - ASSERT_THAT(WriteFd(master_.get(), input, kMaxLineSize), - SyscallSucceedsWithValue(kMaxLineSize)); - int inputBufSize = ASSERT_NO_ERRNO_AND_VALUE( - WaitUntilReceived(replica_.get(), sizeof(kInput1) - 1)); - EXPECT_EQ(inputBufSize, sizeof(kInput1) - 1); -} - -TEST_F(PtyTest, PartialBadBuffer) { - // Allocate 2 pages. - void* addr = mmap(nullptr, 2 * kPageSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - ASSERT_NE(addr, MAP_FAILED); - char* buf = reinterpret_cast<char*>(addr); - - // Guard the 2nd page for our read to run into. - ASSERT_THAT( - mprotect(reinterpret_cast<void*>(buf + kPageSize), kPageSize, PROT_NONE), - SyscallSucceeds()); - - // Leave only one free byte in the buffer. - char* bad_buffer = buf + kPageSize - 1; - - // Write to the master. - constexpr char kBuf[] = "hello\n"; - constexpr size_t size = sizeof(kBuf) - 1; - EXPECT_THAT(WriteFd(master_.get(), kBuf, size), - SyscallSucceedsWithValue(size)); - - // Read from the replica into bad_buffer. - ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), size)); - // Before Linux 3b830a9c this returned EFAULT, but after that commit it - // returns EAGAIN. - EXPECT_THAT( - ReadFd(replica_.get(), bad_buffer, size), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallFailsWithErrno(EAGAIN))); - - EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()) << addr; -} - -TEST_F(PtyTest, SimpleEcho) { - constexpr char kInput[] = "Mr. Eko"; - EXPECT_THAT(WriteFd(master_.get(), kInput, strlen(kInput)), - SyscallSucceedsWithValue(strlen(kInput))); - - char buf[100] = {}; - ExpectReadable(master_, strlen(kInput), buf); - - EXPECT_STREQ(buf, kInput); - ExpectFinished(master_); -} - -TEST_F(PtyTest, GetWindowSize) { - struct winsize ws; - ASSERT_THAT(ioctl(replica_.get(), TIOCGWINSZ, &ws), SyscallSucceeds()); - EXPECT_EQ(ws.ws_row, 0); - EXPECT_EQ(ws.ws_col, 0); -} - -TEST_F(PtyTest, SetReplicaWindowSize) { - constexpr uint16_t kRows = 343; - constexpr uint16_t kCols = 2401; - struct winsize ws = {.ws_row = kRows, .ws_col = kCols}; - ASSERT_THAT(ioctl(replica_.get(), TIOCSWINSZ, &ws), SyscallSucceeds()); - - struct winsize retrieved_ws = {}; - ASSERT_THAT(ioctl(master_.get(), TIOCGWINSZ, &retrieved_ws), - SyscallSucceeds()); - EXPECT_EQ(retrieved_ws.ws_row, kRows); - EXPECT_EQ(retrieved_ws.ws_col, kCols); -} - -TEST_F(PtyTest, SetMasterWindowSize) { - constexpr uint16_t kRows = 343; - constexpr uint16_t kCols = 2401; - struct winsize ws = {.ws_row = kRows, .ws_col = kCols}; - ASSERT_THAT(ioctl(master_.get(), TIOCSWINSZ, &ws), SyscallSucceeds()); - - struct winsize retrieved_ws = {}; - ASSERT_THAT(ioctl(replica_.get(), TIOCGWINSZ, &retrieved_ws), - SyscallSucceeds()); - EXPECT_EQ(retrieved_ws.ws_row, kRows); - EXPECT_EQ(retrieved_ws.ws_col, kCols); -} - -class JobControlTest : public ::testing::Test { - protected: - void SetUp() override { - master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - replica_ = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master_)); - - // Make this a session leader, which also drops the controlling terminal. - // In the gVisor test environment, this test will be run as the session - // leader already (as the sentry init process). - if (!IsRunningOnGvisor()) { - // Ignore failure because setsid(2) fails if the process is already the - // session leader. - setsid(); - ioctl(replica_.get(), TIOCNOTTY); - } - } - - PosixError RunInChild(SubprocessCallback childFunc) { - pid_t child = fork(); - if (!child) { - childFunc(); - _exit(0); - } - int wstatus; - if (waitpid(child, &wstatus, 0) != child) { - return PosixError( - errno, absl::StrCat("child failed with wait status: ", wstatus)); - } - return PosixError(wstatus, "process returned"); - } - - // Master and replica ends of the PTY. Non-blocking. - FileDescriptor master_; - FileDescriptor replica_; -}; - -TEST_F(JobControlTest, SetTTYMaster) { - auto res = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(master_.get(), TIOCSCTTY, 0)); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetTTY) { - auto res = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(ioctl(!replica_.get(), TIOCSCTTY, 0)); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetTTYNonLeader) { - // Fork a process that won't be the session leader. - auto res = - RunInChild([=]() { TEST_PCHECK(ioctl(replica_.get(), TIOCSCTTY, 0)); }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetTTYBadArg) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - auto res = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 1)); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetTTYDifferentSession) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - auto res = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 1)); - - // Fork, join a new session, and try to steal the parent's controlling - // terminal, which should fail. - pid_t grandchild = fork(); - if (!grandchild) { - TEST_PCHECK(setsid() >= 0); - // We shouldn't be able to steal the terminal. - TEST_PCHECK(ioctl(replica_.get(), TIOCSCTTY, 1)); - _exit(0); - } - - int gcwstatus; - TEST_PCHECK(waitpid(grandchild, &gcwstatus, 0) == grandchild); - TEST_PCHECK(gcwstatus == 0); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, ReleaseTTY) { - ASSERT_THAT(ioctl(replica_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Make sure we're ignoring SIGHUP, which will be sent to this process once we - // disconnect they TTY. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - struct sigaction old_sa; - EXPECT_THAT(sigaction(SIGHUP, &sa, &old_sa), SyscallSucceeds()); - EXPECT_THAT(ioctl(replica_.get(), TIOCNOTTY), SyscallSucceeds()); - EXPECT_THAT(sigaction(SIGHUP, &old_sa, NULL), SyscallSucceeds()); -} - -TEST_F(JobControlTest, ReleaseUnsetTTY) { - ASSERT_THAT(ioctl(replica_.get(), TIOCNOTTY), SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(JobControlTest, ReleaseWrongTTY) { - auto res = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - TEST_PCHECK(ioctl(master_.get(), TIOCNOTTY) < 0 && errno == ENOTTY); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, ReleaseTTYNonLeader) { - auto ret = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - pid_t grandchild = fork(); - if (!grandchild) { - TEST_PCHECK(!ioctl(replica_.get(), TIOCNOTTY)); - _exit(0); - } - - int wstatus; - TEST_PCHECK(waitpid(grandchild, &wstatus, 0) == grandchild); - TEST_PCHECK(wstatus == 0); - }); - ASSERT_NO_ERRNO(ret); -} - -TEST_F(JobControlTest, ReleaseTTYDifferentSession) { - auto ret = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - pid_t grandchild = fork(); - if (!grandchild) { - // Join a new session, then try to disconnect. - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(ioctl(replica_.get(), TIOCNOTTY)); - _exit(0); - } - - int wstatus; - TEST_PCHECK(waitpid(grandchild, &wstatus, 0) == grandchild); - TEST_PCHECK(wstatus == 0); - }); - ASSERT_NO_ERRNO(ret); -} - -// Used by the child process spawned in ReleaseTTYSignals to track received -// signals. -static int received; - -void sig_handler(int signum) { received |= signum; } - -// When the session leader releases its controlling terminal, the foreground -// process group gets SIGHUP, then SIGCONT. This test: -// - Spawns 2 threads -// - Has thread 1 return 0 if it gets both SIGHUP and SIGCONT -// - Has thread 2 leave the foreground process group, and return non-zero if it -// receives any signals. -// - Has the parent thread release its controlling terminal -// - Checks that thread 1 got both signals -// - Checks that thread 2 didn't get any signals. -TEST_F(JobControlTest, ReleaseTTYSignals) { - ASSERT_THAT(ioctl(replica_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - received = 0; - struct sigaction sa = {}; - sa.sa_handler = sig_handler; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sigaddset(&sa.sa_mask, SIGHUP); - sigaddset(&sa.sa_mask, SIGCONT); - sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL); - - pid_t same_pgrp_child = fork(); - if (!same_pgrp_child) { - // The child will wait for SIGHUP and SIGCONT, then return 0. It begins with - // SIGHUP and SIGCONT blocked. We install signal handlers for those signals, - // then use sigsuspend to wait for those specific signals. - TEST_PCHECK(!sigaction(SIGHUP, &sa, NULL)); - TEST_PCHECK(!sigaction(SIGCONT, &sa, NULL)); - sigset_t mask; - sigfillset(&mask); - sigdelset(&mask, SIGHUP); - sigdelset(&mask, SIGCONT); - while (received != (SIGHUP | SIGCONT)) { - sigsuspend(&mask); - } - _exit(0); - } - - // We don't want to block these anymore. - sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL); - - // This child will return non-zero if either SIGHUP or SIGCONT are received. - pid_t diff_pgrp_child = fork(); - if (!diff_pgrp_child) { - TEST_PCHECK(!setpgid(0, 0)); - TEST_PCHECK(pause()); - _exit(1); - } - - EXPECT_THAT(setpgid(diff_pgrp_child, diff_pgrp_child), SyscallSucceeds()); - - // Make sure we're ignoring SIGHUP, which will be sent to this process once we - // disconnect they TTY. - struct sigaction sighup_sa = {}; - sighup_sa.sa_handler = SIG_IGN; - sighup_sa.sa_flags = 0; - sigemptyset(&sighup_sa.sa_mask); - struct sigaction old_sa; - EXPECT_THAT(sigaction(SIGHUP, &sighup_sa, &old_sa), SyscallSucceeds()); - - // Release the controlling terminal, sending SIGHUP and SIGCONT to all other - // processes in this process group. - EXPECT_THAT(ioctl(replica_.get(), TIOCNOTTY), SyscallSucceeds()); - - EXPECT_THAT(sigaction(SIGHUP, &old_sa, NULL), SyscallSucceeds()); - - // The child in the same process group will get signaled. - int wstatus; - EXPECT_THAT(waitpid(same_pgrp_child, &wstatus, 0), - SyscallSucceedsWithValue(same_pgrp_child)); - EXPECT_EQ(wstatus, 0); - - // The other child will not get signaled. - EXPECT_THAT(waitpid(diff_pgrp_child, &wstatus, WNOHANG), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(kill(diff_pgrp_child, SIGKILL), SyscallSucceeds()); -} - -TEST_F(JobControlTest, GetForegroundProcessGroup) { - auto res = RunInChild([=]() { - pid_t pid, foreground_pgid; - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 1)); - TEST_PCHECK(!ioctl(replica_.get(), TIOCGPGRP, &foreground_pgid)); - TEST_PCHECK((pid = getpid()) >= 0); - TEST_PCHECK(pid == foreground_pgid); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, GetForegroundProcessGroupNonControlling) { - // At this point there's no controlling terminal, so TIOCGPGRP should fail. - pid_t foreground_pgid; - ASSERT_THAT(ioctl(replica_.get(), TIOCGPGRP, &foreground_pgid), - SyscallFailsWithErrno(ENOTTY)); -} - -// This test: -// - sets itself as the foreground process group -// - creates a child process in a new process group -// - sets that child as the foreground process group -// - kills its child and sets itself as the foreground process group. -// TODO(gvisor.dev/issue/5357): Fix and enable. -TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroup) { - auto res = RunInChild([=]() { - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - // Ignore SIGTTOU so that we don't stop ourself when calling tcsetpgrp. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sigaction(SIGTTOU, &sa, NULL); - - // Set ourself as the foreground process group. - TEST_PCHECK(!tcsetpgrp(replica_.get(), getpgid(0))); - - // Create a new process that just waits to be signaled. - pid_t grandchild = fork(); - if (!grandchild) { - TEST_PCHECK(!pause()); - // We should never reach this. - _exit(1); - } - - // Make the child its own process group, then make it the controlling - // process group of the terminal. - TEST_PCHECK(!setpgid(grandchild, grandchild)); - TEST_PCHECK(!tcsetpgrp(replica_.get(), grandchild)); - - // Sanity check - we're still the controlling session. - TEST_PCHECK(getsid(0) == getsid(grandchild)); - - // Signal the child, wait for it to exit, then retake the terminal. - TEST_PCHECK(!kill(grandchild, SIGTERM)); - int wstatus; - TEST_PCHECK(waitpid(grandchild, &wstatus, 0) == grandchild); - TEST_PCHECK(WIFSIGNALED(wstatus)); - TEST_PCHECK(WTERMSIG(wstatus) == SIGTERM); - - // Set ourself as the foreground process. - pid_t pgid; - TEST_PCHECK(pgid = getpgid(0) == 0); - TEST_PCHECK(!tcsetpgrp(replica_.get(), pgid)); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupWrongTTY) { - pid_t pid = getpid(); - ASSERT_THAT(ioctl(replica_.get(), TIOCSPGRP, &pid), - SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupNegPgid) { - auto ret = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - pid_t pid = -1; - TEST_PCHECK(ioctl(replica_.get(), TIOCSPGRP, &pid) && errno == EINVAL); - }); - ASSERT_NO_ERRNO(ret); -} - -// TODO(gvisor.dev/issue/5357): Fix and enable. -TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroupEmptyProcessGroup) { - auto res = RunInChild([=]() { - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - // Create a new process, put it in a new process group, make that group the - // foreground process group, then have the process wait. - pid_t grandchild = fork(); - if (!grandchild) { - TEST_PCHECK(!setpgid(0, 0)); - _exit(0); - } - - // Wait for the child to exit. - int wstatus; - TEST_PCHECK(waitpid(grandchild, &wstatus, 0) == grandchild); - // The child's process group doesn't exist anymore - this should fail. - TEST_PCHECK(ioctl(replica_.get(), TIOCSPGRP, &grandchild) != 0 && - errno == ESRCH); - }); - ASSERT_NO_ERRNO(res); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupDifferentSession) { - auto ret = RunInChild([=]() { - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); - - int sync_setsid[2]; - int sync_exit[2]; - TEST_PCHECK(pipe(sync_setsid) >= 0); - TEST_PCHECK(pipe(sync_exit) >= 0); - - // Create a new process and put it in a new session. - pid_t grandchild = fork(); - if (!grandchild) { - TEST_PCHECK(setsid() >= 0); - // Tell the parent we're in a new session. - char c = 'c'; - TEST_PCHECK(WriteFd(sync_setsid[1], &c, 1) == 1); - TEST_PCHECK(ReadFd(sync_exit[0], &c, 1) == 1); - _exit(0); - } - - // Wait for the child to tell us it's in a new session. - char c = 'c'; - TEST_PCHECK(ReadFd(sync_setsid[0], &c, 1) == 1); - - // Child is in a new session, so we can't make it the foregroup process - // group. - TEST_PCHECK(ioctl(replica_.get(), TIOCSPGRP, &grandchild) && - errno == EPERM); - - TEST_PCHECK(WriteFd(sync_exit[1], &c, 1) == 1); - - int wstatus; - TEST_PCHECK(waitpid(grandchild, &wstatus, 0) == grandchild); - TEST_PCHECK(WIFEXITED(wstatus)); - TEST_PCHECK(!WEXITSTATUS(wstatus)); - }); - ASSERT_NO_ERRNO(ret); -} - -// Verify that we don't hang when creating a new session from an orphaned -// process group (b/139968068). Calling setsid() creates an orphaned process -// group, as process groups that contain the session's leading process are -// orphans. -// -// We create 2 sessions in this test. The init process in gVisor is considered -// not to be an orphan (see sessions.go), so we have to create a session from -// which to create a session. The latter session is being created from an -// orphaned process group. -TEST_F(JobControlTest, OrphanRegression) { - pid_t session_2_leader = fork(); - if (!session_2_leader) { - TEST_PCHECK(setsid() >= 0); - - pid_t session_3_leader = fork(); - if (!session_3_leader) { - TEST_PCHECK(setsid() >= 0); - - _exit(0); - } - - int wstatus; - TEST_PCHECK(waitpid(session_3_leader, &wstatus, 0) == session_3_leader); - TEST_PCHECK(wstatus == 0); - - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(session_2_leader, &wstatus, 0), - SyscallSucceedsWithValue(session_2_leader)); - ASSERT_EQ(wstatus, 0); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pty_root.cc b/test/syscalls/linux/pty_root.cc deleted file mode 100644 index 4ac648729..000000000 --- a/test/syscalls/linux/pty_root.cc +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/ioctl.h> -#include <termios.h> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/pty_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// StealTTY tests whether privileged processes can steal controlling terminals. -// If the stealing process has CAP_SYS_ADMIN in the root user namespace, the -// test ensures that stealing works. If it has non-root CAP_SYS_ADMIN, it -// ensures stealing fails. -TEST(JobControlRootTest, StealTTY) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - bool true_root = true; - if (!IsRunningOnGvisor()) { - // If running in Linux, we may only have CAP_SYS_ADMIN in a non-root user - // namespace (i.e. we are not truly root). We use init_module as a proxy for - // whether we are true root, as it returns EPERM immediately. - ASSERT_THAT(syscall(SYS_init_module, nullptr, 0, nullptr), SyscallFails()); - true_root = errno != EPERM; - - // Make this a session leader, which also drops the controlling terminal. - // In the gVisor test environment, this test will be run as the session - // leader already (as the sentry init process). - ASSERT_THAT(setsid(), SyscallSucceeds()); - } - - FileDescriptor master = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(OpenReplica(master)); - - // Make replica the controlling terminal. - ASSERT_THAT(ioctl(replica.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Fork, join a new session, and try to steal the parent's controlling - // terminal, which should succeed when we have CAP_SYS_ADMIN and pass an arg - // of 1. - pid_t child = fork(); - if (!child) { - ASSERT_THAT(setsid(), SyscallSucceeds()); - // We shouldn't be able to steal the terminal with the wrong arg value. - TEST_PCHECK(ioctl(replica.get(), TIOCSCTTY, 0)); - // We should be able to steal it if we are true root. - TEST_PCHECK(true_root == !ioctl(replica.get(), TIOCSCTTY, 1)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pwrite64.cc b/test/syscalls/linux/pwrite64.cc deleted file mode 100644 index 1b2f25363..000000000 --- a/test/syscalls/linux/pwrite64.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/unistd.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// TODO(gvisor.dev/issue/2370): This test is currently very rudimentary. -class Pwrite64 : public ::testing::Test { - void SetUp() override { - name_ = NewTempAbsPath(); - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_CREAT, 0644), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - } - - void TearDown() override { unlink(name_.c_str()); } - - public: - std::string name_; -}; - -TEST_F(Pwrite64, AppendOnly) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_APPEND | O_RDWR), SyscallSucceeds()); - constexpr int64_t kBufSize = 1024; - std::vector<char> buf(kBufSize); - std::fill(buf.begin(), buf.end(), 'a'); - EXPECT_THAT(PwriteFd(fd, buf.data(), buf.size(), 0), - SyscallSucceedsWithValue(buf.size())); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(Pwrite64, InvalidArgs) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_APPEND | O_RDWR), SyscallSucceeds()); - constexpr int64_t kBufSize = 1024; - std::vector<char> buf(kBufSize); - std::fill(buf.begin(), buf.end(), 'a'); - EXPECT_THAT(PwriteFd(fd, buf.data(), buf.size(), -1), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(Pwrite64, Overflow) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_APPEND | O_RDWR), SyscallSucceeds()); - constexpr int64_t kBufSize = 1024; - std::vector<char> buf(kBufSize); - std::fill(buf.begin(), buf.end(), 'a'); - EXPECT_THAT(PwriteFd(fd, buf.data(), buf.size(), 0x7fffffffffffffffull), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(Pwrite64, Pwrite64WithOpath) { - 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_PATH)); - - std::vector<char> buf(1); - EXPECT_THAT(PwriteFd(fd.get(), buf.data(), 1, 0), - SyscallFailsWithErrno(EBADF)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc deleted file mode 100644 index 00aed61b4..000000000 --- a/test/syscalls/linux/pwritev2.cc +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/uio.h> - -#include <string> -#include <vector> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#ifndef SYS_pwritev2 -#if defined(__x86_64__) -#define SYS_pwritev2 328 -#elif defined(__aarch64__) -#define SYS_pwritev2 287 -#else -#error "Unknown architecture" -#endif -#endif // SYS_pwrite2 - -#ifndef RWF_HIPRI -#define RWF_HIPRI 0x1 -#endif // RWF_HIPRI - -#ifndef RWF_DSYNC -#define RWF_DSYNC 0x2 -#endif // RWF_DSYNC - -#ifndef RWF_SYNC -#define RWF_SYNC 0x4 -#endif // RWF_SYNC - -constexpr int kBufSize = 1024; - -void SetContent(std::vector<char>& content) { - for (uint i = 0; i < content.size(); i++) { - content[i] = static_cast<char>((i % 10) + '0'); - } -} - -ssize_t pwritev2(unsigned long fd, const struct iovec* iov, - unsigned long iovcnt, off_t offset, unsigned long flags) { - // syscall on pwritev2 does some weird things (see man syscall and search - // pwritev2), so we insert a 0 to word align the flags argument on native. - return syscall(SYS_pwritev2, fd, iov, iovcnt, offset, 0, flags); -} - -// This test is the base case where we call pwritev (no offset, no flags). -TEST(Writev2Test, BaseCall) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - std::vector<char> content(kBufSize); - SetContent(content); - struct iovec iov[2]; - iov[0].iov_base = content.data(); - iov[0].iov_len = content.size() / 2; - iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2); - iov[1].iov_len = content.size() / 2; - - ASSERT_THAT(pwritev2(fd.get(), iov, /*iovcnt=*/2, - /*offset=*/0, /*flags=*/0), - SyscallSucceedsWithValue(kBufSize)); - - std::vector<char> buf(kBufSize); - EXPECT_THAT(read(fd.get(), buf.data(), kBufSize), - SyscallSucceedsWithValue(kBufSize)); - - EXPECT_EQ(content, buf); -} - -// This test is where we call pwritev2 with a positive offset and no flags. -TEST(Pwritev2Test, ValidPositiveOffset) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - std::string prefix(kBufSize, '0'); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), prefix, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - std::vector<char> content(kBufSize); - SetContent(content); - struct iovec iov; - iov.iov_base = content.data(); - iov.iov_len = content.size(); - - ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/prefix.size(), /*flags=*/0), - SyscallSucceedsWithValue(content.size())); - - std::vector<char> buf(prefix.size() + content.size()); - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - std::vector<char> want(prefix.begin(), prefix.end()); - want.insert(want.end(), content.begin(), content.end()); - EXPECT_EQ(want, buf); -} - -// This test is the base case where we call writev by using -1 as the offset. -// The write should use the file offset, so the test increments the file offset -// prior to call pwritev2. -TEST(Pwritev2Test, NegativeOneOffset) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const std::string prefix = "00"; - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), prefix.data(), TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET), - SyscallSucceedsWithValue(prefix.size())); - - std::vector<char> content(kBufSize); - SetContent(content); - struct iovec iov; - iov.iov_base = content.data(); - iov.iov_len = content.size(); - - ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt*/ 1, - /*offset=*/static_cast<off_t>(-1), /*flags=*/0), - SyscallSucceedsWithValue(content.size())); - - ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(prefix.size() + content.size())); - - std::vector<char> buf(prefix.size() + content.size()); - EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0), - SyscallSucceedsWithValue(buf.size())); - - std::vector<char> want(prefix.begin(), prefix.end()); - want.insert(want.end(), content.begin(), content.end()); - EXPECT_EQ(want, buf); -} - -// pwritev2 requires if the RWF_HIPRI flag is passed, the fd must be opened with -// O_DIRECT. This test implements a correct call with the RWF_HIPRI flag. -TEST(Pwritev2Test, CallWithRWF_HIPRI) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - std::vector<char> content(kBufSize); - SetContent(content); - struct iovec iov; - iov.iov_base = content.data(); - iov.iov_len = content.size(); - - EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/0, /*flags=*/RWF_HIPRI), - SyscallSucceedsWithValue(kBufSize)); - - std::vector<char> buf(content.size()); - EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_EQ(buf, content); -} - -// This test calls pwritev2 with a bad file descriptor. -TEST(Writev2Test, BadFile) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - ASSERT_THAT(pwritev2(/*fd=*/-1, /*iov=*/nullptr, /*iovcnt=*/0, - /*offset=*/0, /*flags=*/0), - SyscallFailsWithErrno(EBADF)); -} - -// This test calls pwrite2 with an invalid offset. -TEST(Pwritev2Test, InvalidOffset) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/static_cast<off_t>(-8), /*flags=*/0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(Pwritev2Test, UnseekableFileValid) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - int pipe_fds[2]; - - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - std::vector<char> content(32, '0'); - SetContent(content); - struct iovec iov; - iov.iov_base = content.data(); - iov.iov_len = content.size(); - - EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1, - /*offset=*/static_cast<off_t>(-1), /*flags=*/0), - SyscallSucceedsWithValue(content.size())); - - std::vector<char> buf(content.size()); - EXPECT_THAT(read(pipe_fds[0], buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_EQ(content, buf); - - EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -// Calling pwritev2 with a non-negative offset calls pwritev. Calling pwritev -// with an unseekable file is not allowed. A pipe is used for an unseekable -// file. -TEST(Pwritev2Test, UnseekableFileInvalid) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - int pipe_fds[2]; - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - - EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1, - /*offset=*/2, /*flags=*/0), - SyscallFailsWithErrno(ESPIPE)); - - EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); -} - -TEST(Pwritev2Test, ReadOnlyFile) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/0, /*flags=*/0), - SyscallFailsWithErrno(EBADF)); -} - -TEST(Pwritev2Test, Pwritev2WithOpath) { - SKIP_IF(IsRunningWithVFS1()); - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/0), - SyscallFailsWithErrno(EBADF)); -} - -// This test calls pwritev2 with an invalid flag. -TEST(Pwritev2Test, InvalidFlag) { - SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR | O_DIRECT)); - - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, - /*offset=*/0, /*flags=*/0xF0), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc deleted file mode 100644 index 32924466f..000000000 --- a/test/syscalls/linux/raw_socket.cc +++ /dev/null @@ -1,902 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/capability.h> -#include <linux/filter.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/ip6.h> -#include <netinet/ip_icmp.h> -#include <poll.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Note: in order to run these tests, /proc/sys/net/ipv4/ping_group_range will -// need to be configured to let the superuser create ping sockets (see icmp(7)). - -namespace gvisor { -namespace testing { - -namespace { - -// Fixture for tests parameterized by protocol. -class RawSocketTest : public ::testing::TestWithParam<std::tuple<int, int>> { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Sends buf via s_. - void SendBuf(const char* buf, int buf_len); - - // Reads from s_ into recv_buf. - void ReceiveBuf(char* recv_buf, size_t recv_buf_len); - - void ReceiveBufFrom(int sock, char* recv_buf, size_t recv_buf_len); - - int Protocol() { return std::get<0>(GetParam()); } - - int Family() { return std::get<1>(GetParam()); } - - socklen_t AddrLen() { - if (Family() == AF_INET) { - return sizeof(sockaddr_in); - } - return sizeof(sockaddr_in6); - } - - int HdrLen() { - if (Family() == AF_INET) { - return sizeof(struct iphdr); - } - // IPv6 raw sockets don't include the header. - return 0; - } - - // The socket used for both reading and writing. - int s_; - - // The loopback address. - struct sockaddr_storage addr_; -}; - -void RawSocketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(Family(), SOCK_RAW, Protocol()), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - ASSERT_THAT(s_ = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); - - addr_ = {}; - - // We don't set ports because raw sockets don't have a notion of ports. - if (Family() == AF_INET) { - struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr_); - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - } else { - struct sockaddr_in6* sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr_); - sin6->sin6_family = AF_INET6; - sin6->sin6_addr = in6addr_loopback; - } -} - -void RawSocketTest::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(s_), SyscallSucceeds()); - } -} - -// We should be able to create multiple raw sockets for the same protocol. -// BasicRawSocket::Setup creates the first one, so we only have to create one -// more here. -TEST_P(RawSocketTest, MultipleCreation) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int s2; - ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); - - ASSERT_THAT(close(s2), SyscallSucceeds()); -} - -// Test that shutting down an unconnected socket fails. -TEST_P(RawSocketTest, FailShutdownWithoutConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); - ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); -} - -// Shutdown is a no-op for raw sockets (and datagram sockets in general). -TEST_P(RawSocketTest, ShutdownWriteNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "noop"; - ASSERT_THAT(RetryEINTR(write)(s_, kBuf, sizeof(kBuf)), - SyscallSucceedsWithValue(sizeof(kBuf))); -} - -// Shutdown is a no-op for raw sockets (and datagram sockets in general). -TEST_P(RawSocketTest, ShutdownReadNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "gdg"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - std::vector<char> c(sizeof(kBuf) + HdrLen()); - ASSERT_THAT(read(s_, c.data(), c.size()), SyscallSucceedsWithValue(c.size())); -} - -// Test that listen() fails. -TEST_P(RawSocketTest, FailListen) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP)); -} - -// Test that accept() fails. -TEST_P(RawSocketTest, FailAccept) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - struct sockaddr saddr; - socklen_t addrlen; - ASSERT_THAT(accept(s_, &saddr, &addrlen), SyscallFailsWithErrno(ENOTSUP)); -} - -// Test that getpeername() returns nothing before connect(). -TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - struct sockaddr saddr; - socklen_t addrlen = sizeof(saddr); - ASSERT_THAT(getpeername(s_, &saddr, &addrlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -// Test that getpeername() returns something after connect(). -TEST_P(RawSocketTest, GetPeerName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - struct sockaddr saddr; - socklen_t addrlen = sizeof(saddr); - ASSERT_THAT(getpeername(s_, &saddr, &addrlen), - SyscallFailsWithErrno(ENOTCONN)); - ASSERT_GT(addrlen, 0); -} - -// Test that the socket is writable immediately. -TEST_P(RawSocketTest, PollWritableImmediately) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - struct pollfd pfd = {}; - pfd.fd = s_; - pfd.events = POLLOUT; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1)); -} - -// Test that the socket isn't readable before receiving anything. -TEST_P(RawSocketTest, PollNotReadableInitially) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Try to receive data with MSG_DONTWAIT, which returns immediately if there's - // nothing to be read. - char buf[117]; - ASSERT_THAT(RetryEINTR(recv)(s_, buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Test that the socket becomes readable once something is written to it. -TEST_P(RawSocketTest, PollTriggeredOnWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Write something so that there's data to be read. - // Arbitrary. - constexpr char kBuf[] = "JP5"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - struct pollfd pfd = {}; - pfd.fd = s_; - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1)); -} - -// Test that we can connect() to a valid IP (loopback). -TEST_P(RawSocketTest, ConnectToLoopback) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); -} - -// Test that calling send() without connect() fails. -TEST_P(RawSocketTest, SendWithoutConnectFails) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Arbitrary. - constexpr char kBuf[] = "Endgame was good"; - ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0), - SyscallFailsWithErrno(EDESTADDRREQ)); -} - -// Wildcard Bind. -TEST_P(RawSocketTest, BindToWildcard) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - struct sockaddr_storage addr; - addr = {}; - - // We don't set ports because raw sockets don't have a notion of ports. - if (Family() == AF_INET) { - struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = htonl(INADDR_ANY); - } else { - struct sockaddr_in6* sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); - sin6->sin6_family = AF_INET6; - sin6->sin6_addr = in6addr_any; - } - - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); -} - -// Bind to localhost. -TEST_P(RawSocketTest, BindToLocalhost) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); -} - -// Bind to a different address. -TEST_P(RawSocketTest, BindToInvalid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - struct sockaddr_storage bind_addr = addr_; - if (Family() == AF_INET) { - struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&bind_addr); - sin->sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to. - } else { - struct sockaddr_in6* sin6 = - reinterpret_cast<struct sockaddr_in6*>(&bind_addr); - memset(&sin6->sin6_addr.s6_addr, 0, sizeof(sin6->sin6_addr.s6_addr)); - sin6->sin6_addr.s6_addr[0] = 1; // 1: - An address that we can't bind to. - } - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), - AddrLen()), SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -// Send and receive an packet. -TEST_P(RawSocketTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Arbitrary. - constexpr char kBuf[] = "TB12"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0); -} - -// We should be able to create multiple raw sockets for the same protocol and -// receive the same packet on both. -TEST_P(RawSocketTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int s2; - ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "TB10"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - // Receive it on socket 1. - std::vector<char> recv_buf1(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf1.data(), recv_buf1.size())); - - // Receive it on socket 2. - std::vector<char> recv_buf2(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s2, recv_buf2.data(), - recv_buf2.size())); - - EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(), - recv_buf2.data() + HdrLen(), sizeof(kBuf)), - 0); - - ASSERT_THAT(close(s2), SyscallSucceeds()); -} - -// Test that connect sends packets to the right place. -TEST_P(RawSocketTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "JH4"; - ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0), - SyscallSucceedsWithValue(sizeof(kBuf))); - - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0); -} - -// Bind to localhost, then send and receive packets. -TEST_P(RawSocketTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "DR16"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0); -} - -// Bind and connect to localhost and send/receive packets. -TEST_P(RawSocketTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - - // Arbitrary. - constexpr char kBuf[] = "DG88"; - ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf))); - - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0); -} - -// Check that setting SO_RCVBUF below min is clamped to the minimum -// receive buffer size. -TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover minimum receive buf size by trying to set it to zero. - // See: - // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820 - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &below_min, sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_RCVBUF above max is clamped to the maximum -// receive buffer size. -TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover max buf size by trying to set the largest possible buffer size. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &above_max, sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. -TEST_P(RawSocketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int max = 0; - int min = 0; - { - // Discover max buf size by trying to set a really large buffer size. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by trying to set a zero size receive buffer - // size. - // See: - // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820 - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &quarter_sz, sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - // TODO(gvisor.dev/issue/2926): Remove when Netstack matches linux behavior. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - ASSERT_EQ(quarter_sz, val); -} - -// Check that setting SO_SNDBUF below min is clamped to the minimum -// receive buffer size. -TEST_P(RawSocketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover minimum buffer size by trying to set it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &below_min, sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_SNDBUF above max is clamped to the maximum -// send buffer size. -TEST_P(RawSocketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Discover maximum buffer size by trying to set it to a large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &above_max, sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. -TEST_P(RawSocketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int max = 0; - int min = 0; - { - // Discover maximum buffer size by trying to set it to a large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by trying to set it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &quarter_sz, sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - quarter_sz *= 2; - ASSERT_EQ(quarter_sz, val); -} - -// Test that receive buffer limits are not enforced when the recv buffer is -// empty. -TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - - int min = 0; - { - // Discover minimum buffer size by trying to set it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - { - // Send data of size min and verify that it's received. - std::vector<char> buf(min); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(buf.size() + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); - } - - { - // Send data of size min + 1 and verify that its received. Both linux and - // Netstack accept a dgram that exceeds rcvBuf limits if the receive buffer - // is currently empty. - std::vector<char> buf(min + 1); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(buf.size() + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); - } -} - -TEST_P(RawSocketTest, RecvBufLimits) { - // TCP stack generates RSTs for unknown endpoints and it complicates the test - // as we have to deal with the RST packets as well. For testing the raw socket - // endpoints buffer limit enforcement we can just test for UDP. - // - // We don't use SKIP_IF here because root_test_runner explicitly fails if a - // test is skipped. - if (Protocol() == IPPROTO_TCP) { - return; - } - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); - - int min = 0; - { - // Discover minimum buffer size by trying to set it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - // Now set the limit to min * 2. - int new_rcv_buf_sz = min * 4; - if (!IsRunningOnGvisor()) { - // Linux doubles the value specified so just set to min. - new_rcv_buf_sz = min * 2; - } - - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &new_rcv_buf_sz, - sizeof(new_rcv_buf_sz)), - SyscallSucceeds()); - int rcv_buf_sz = 0; - { - socklen_t rcv_buf_len = sizeof(rcv_buf_sz); - ASSERT_THAT( - getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz, &rcv_buf_len), - SyscallSucceeds()); - } - - // Set a receive timeout so that we don't block forever on reads if the test - // fails. - struct timeval tv { - .tv_sec = 1, .tv_usec = 0, - }; - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - { - std::vector<char> buf(min); - RandomizeBuffer(buf.data(), buf.size()); - - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - int sent = 4; - if (IsRunningOnGvisor()) { - // Linux seems to drop the 4th packet even though technically it should - // fit in the receive buffer. - ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size())); - sent++; - } - - // Verify that the expected number of packets are available to be read. - for (int i = 0; i < sent - 1; i++) { - // Receive the packet and make sure it's identical. - std::vector<char> recv_buf(buf.size() + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), - buf.size()), - 0); - } - - // Assert that the last packet is dropped because the receive buffer should - // be full after the first four packets. - std::vector<char> recv_buf(buf.size() + HdrLen()); - struct iovec iov = {}; - iov.iov_base = static_cast<void*>(const_cast<char*>(recv_buf.data())); - iov.iov_len = buf.size(); - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); - } -} - -void RawSocketTest::SendBuf(const char* buf, int buf_len) { - // It's safe to use const_cast here because sendmsg won't modify the iovec or - // address. - struct iovec iov = {}; - iov.iov_base = static_cast<void*>(const_cast<char*>(buf)); - iov.iov_len = static_cast<size_t>(buf_len); - struct msghdr msg = {}; - msg.msg_name = static_cast<void*>(&addr_); - msg.msg_namelen = AddrLen(); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - ASSERT_THAT(sendmsg(s_, &msg, 0), SyscallSucceedsWithValue(buf_len)); -} - -void RawSocketTest::ReceiveBuf(char* recv_buf, size_t recv_buf_len) { - ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s_, recv_buf, recv_buf_len)); -} - -void RawSocketTest::ReceiveBufFrom(int sock, char* recv_buf, - size_t recv_buf_len) { - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sock, recv_buf, recv_buf_len)); -} - -TEST_P(RawSocketTest, SetSocketDetachFilterNoInstalledFilter) { - // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. - if (IsRunningOnGvisor()) { - constexpr int val = 0; - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallSucceeds()); - return; - } - - constexpr int val = 0; - ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(RawSocketTest, GetSocketDetachFilter) { - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len), - SyscallFailsWithErrno(ENOPROTOOPT)); -} - -// AF_INET6+SOCK_RAW+IPPROTO_RAW sockets can be created, but not written to. -TEST(RawSocketTest, IPv6ProtoRaw) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int sock; - ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), - SyscallSucceeds()); - - // Verify that writing yields EINVAL. - char buf[] = "This is such a weird little edge case"; - struct sockaddr_in6 sin6 = {}; - sin6.sin6_family = AF_INET6; - sin6.sin6_addr = in6addr_loopback; - ASSERT_THAT(sendto(sock, buf, sizeof(buf), 0 /* flags */, - reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)), - SyscallFailsWithErrno(EINVAL)); -} - -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), - ::testing::Values(AF_INET, AF_INET6))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc deleted file mode 100644 index 2f25aceb2..000000000 --- a/test/syscalls/linux/raw_socket_hdrincl.cc +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/capability.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <netinet/udp.h> -#include <poll.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <cstring> - -#include "gtest/gtest.h" -#include "absl/base/internal/endian.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Tests for IPPROTO_RAW raw sockets, which implies IP_HDRINCL. -class RawHDRINCL : public ::testing::Test { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Returns a valid looback IP header with no payload. - struct iphdr LoopbackHeader(); - - // Fills in buf with an IP header, UDP header, and payload. Returns false if - // buf_size isn't large enough to hold everything. - bool FillPacket(char* buf, size_t buf_size, int port, const char* payload, - uint16_t payload_size); - - // The socket used for both reading and writing. - int socket_; - - // The loopback address. - struct sockaddr_in addr_; -}; - -void RawHDRINCL::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_RAW), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - ASSERT_THAT(socket_ = socket(AF_INET, SOCK_RAW, IPPROTO_RAW), - SyscallSucceeds()); - - addr_ = {}; - - addr_.sin_port = IPPROTO_IP; - addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr_.sin_family = AF_INET; -} - -void RawHDRINCL::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(socket_), SyscallSucceeds()); - } -} - -struct iphdr RawHDRINCL::LoopbackHeader() { - struct iphdr hdr = {}; - hdr.ihl = 5; - hdr.version = 4; - hdr.tos = 0; - hdr.tot_len = absl::gbswap_16(sizeof(hdr)); - hdr.id = 0; - hdr.frag_off = 0; - hdr.ttl = 7; - hdr.protocol = 1; - hdr.daddr = htonl(INADDR_LOOPBACK); - // hdr.check is set by the network stack. - // hdr.tot_len is set by the network stack. - // hdr.saddr is set by the network stack. - return hdr; -} - -bool RawHDRINCL::FillPacket(char* buf, size_t buf_size, int port, - const char* payload, uint16_t payload_size) { - if (buf_size < sizeof(struct iphdr) + sizeof(struct udphdr) + payload_size) { - return false; - } - - struct iphdr ip = LoopbackHeader(); - ip.protocol = IPPROTO_UDP; - - struct udphdr udp = {}; - udp.source = absl::gbswap_16(port); - udp.dest = absl::gbswap_16(port); - udp.len = absl::gbswap_16(sizeof(udp) + payload_size); - udp.check = 0; - - memcpy(buf, reinterpret_cast<char*>(&ip), sizeof(ip)); - memcpy(buf + sizeof(ip), reinterpret_cast<char*>(&udp), sizeof(udp)); - memcpy(buf + sizeof(ip) + sizeof(udp), payload, payload_size); - - return true; -} - -// We should be able to create multiple IPPROTO_RAW sockets. RawHDRINCL::Setup -// creates the first one, so we only have to create one more here. -TEST_F(RawHDRINCL, MultipleCreation) { - int s2; - ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, IPPROTO_RAW), SyscallSucceeds()); - - ASSERT_THAT(close(s2), SyscallSucceeds()); -} - -// Test that shutting down an unconnected socket fails. -TEST_F(RawHDRINCL, FailShutdownWithoutConnect) { - ASSERT_THAT(shutdown(socket_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); - ASSERT_THAT(shutdown(socket_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); -} - -// Test that listen() fails. -TEST_F(RawHDRINCL, FailListen) { - ASSERT_THAT(listen(socket_, 1), SyscallFailsWithErrno(ENOTSUP)); -} - -// Test that accept() fails. -TEST_F(RawHDRINCL, FailAccept) { - struct sockaddr saddr; - socklen_t addrlen; - ASSERT_THAT(accept(socket_, &saddr, &addrlen), - SyscallFailsWithErrno(ENOTSUP)); -} - -// Test that the socket is writable immediately. -TEST_F(RawHDRINCL, PollWritableImmediately) { - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLOUT; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 0), SyscallSucceedsWithValue(1)); -} - -// Test that the socket isn't readable. -TEST_F(RawHDRINCL, NotReadable) { - // Try to receive data with MSG_DONTWAIT, which returns immediately if there's - // nothing to be read. - char buf[117]; - ASSERT_THAT(RetryEINTR(recv)(socket_, buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Test that we can connect() to a valid IP (loopback). -TEST_F(RawHDRINCL, ConnectToLoopback) { - ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceeds()); -} - -TEST_F(RawHDRINCL, SendWithoutConnectSucceeds) { - // FIXME(gvisor.dev/issue/3159): Test currently flaky. - SKIP_IF(true); - - struct iphdr hdr = LoopbackHeader(); - ASSERT_THAT(send(socket_, &hdr, sizeof(hdr), 0), - SyscallSucceedsWithValue(sizeof(hdr))); -} - -// HDRINCL implies write-only. Verify that we can't read a packet sent to -// loopback. -TEST_F(RawHDRINCL, NotReadableAfterWrite) { - ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceeds()); - - // Construct a packet with an IP header, UDP header, and payload. - constexpr char kPayload[] = "odst"; - char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)]; - ASSERT_TRUE(FillPacket(packet, sizeof(packet), 40000 /* port */, kPayload, - sizeof(kPayload))); - - socklen_t addrlen = sizeof(addr_); - ASSERT_NO_FATAL_FAILURE( - sendto(socket_, reinterpret_cast<void*>(&packet), sizeof(packet), 0, - reinterpret_cast<struct sockaddr*>(&addr_), addrlen)); - - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); -} - -TEST_F(RawHDRINCL, WriteTooSmall) { - ASSERT_THAT(connect(socket_, reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceeds()); - - // This is smaller than the size of an IP header. - constexpr char kBuf[] = "JP5"; - ASSERT_THAT(send(socket_, kBuf, sizeof(kBuf), 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Bind to localhost. -TEST_F(RawHDRINCL, BindToLocalhost) { - ASSERT_THAT( - bind(socket_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), - SyscallSucceeds()); -} - -// Bind to a different address. -TEST_F(RawHDRINCL, BindToInvalid) { - struct sockaddr_in bind_addr = {}; - bind_addr.sin_family = AF_INET; - bind_addr.sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to. - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -// Send and receive a packet. -TEST_F(RawHDRINCL, SendAndReceive) { - int port = 40000; - if (!IsRunningOnGvisor()) { - port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE( - PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false))); - } - - // IPPROTO_RAW sockets are write-only. We'll have to open another socket to - // read what we write. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); - - // Construct a packet with an IP header, UDP header, and payload. - constexpr char kPayload[] = "toto"; - char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)]; - ASSERT_TRUE( - FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload))); - - socklen_t addrlen = sizeof(addr_); - ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0, - reinterpret_cast<struct sockaddr*>(&addr_), - addrlen)); - - // Receive the payload. - char recv_buf[sizeof(packet)]; - struct sockaddr_in src; - socklen_t src_size = sizeof(src); - ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_size), - SyscallSucceedsWithValue(sizeof(packet))); - EXPECT_EQ( - memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr), - sizeof(kPayload)), - 0); - // The network stack should have set the source address. - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK); - // The packet ID should not be 0, as the packet has DF=0. - struct iphdr* iphdr = reinterpret_cast<struct iphdr*>(recv_buf); - EXPECT_NE(iphdr->id, 0); -} - -// Send and receive a packet where the sendto address is not the same as the -// provided destination. -TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) { - // FIXME(gvisor.dev/issue/3160): Test currently flaky. - SKIP_IF(true); - - int port = 40000; - if (!IsRunningOnGvisor()) { - port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE( - PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false))); - } - - // IPPROTO_RAW sockets are write-only. We'll have to open another socket to - // read what we write. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); - - // Construct a packet with an IP header, UDP header, and payload. - constexpr char kPayload[] = "toto"; - char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)]; - ASSERT_TRUE( - FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload))); - // Overwrite the IP destination address with an IP we can't get to. - struct iphdr iphdr = {}; - memcpy(&iphdr, packet, sizeof(iphdr)); - iphdr.daddr = 42; - memcpy(packet, &iphdr, sizeof(iphdr)); - - socklen_t addrlen = sizeof(addr_); - ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0, - reinterpret_cast<struct sockaddr*>(&addr_), - addrlen)); - - // Receive the payload, since sendto should replace the bad destination with - // localhost. - char recv_buf[sizeof(packet)]; - struct sockaddr_in src; - socklen_t src_size = sizeof(src); - ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_size), - SyscallSucceedsWithValue(sizeof(packet))); - EXPECT_EQ( - memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr), - sizeof(kPayload)), - 0); - // The network stack should have set the source address. - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK); - // The packet ID should not be 0, as the packet has DF=0. - struct iphdr recv_iphdr = {}; - memcpy(&recv_iphdr, recv_buf, sizeof(recv_iphdr)); - EXPECT_NE(recv_iphdr.id, 0); - // The destination address should be localhost, not the bad IP we set - // initially. - EXPECT_EQ(absl::gbswap_32(recv_iphdr.daddr), INADDR_LOOPBACK); -} - -// Send and receive a packet w/ the IP_HDRINCL option set. -TEST_F(RawHDRINCL, SendAndReceiveIPHdrIncl) { - int port = 40000; - if (!IsRunningOnGvisor()) { - port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE( - PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false))); - } - - FileDescriptor recv_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); - - FileDescriptor send_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); - - // Enable IP_HDRINCL option so that we can build and send w/ an IP - // header. - constexpr int kSockOptOn = 1; - ASSERT_THAT(setsockopt(send_sock.get(), SOL_IP, IP_HDRINCL, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - // This is not strictly required but we do it to make sure that setting - // IP_HDRINCL on a non IPPROTO_RAW socket does not prevent it from receiving - // packets. - ASSERT_THAT(setsockopt(recv_sock.get(), SOL_IP, IP_HDRINCL, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Construct a packet with an IP header, UDP header, and payload. - constexpr char kPayload[] = "toto"; - char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)]; - ASSERT_TRUE( - FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload))); - - socklen_t addrlen = sizeof(addr_); - ASSERT_NO_FATAL_FAILURE(sendto(send_sock.get(), &packet, sizeof(packet), 0, - reinterpret_cast<struct sockaddr*>(&addr_), - addrlen)); - - // Receive the payload. - char recv_buf[sizeof(packet)]; - struct sockaddr_in src; - socklen_t src_size = sizeof(src); - ASSERT_THAT(recvfrom(recv_sock.get(), recv_buf, sizeof(recv_buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_size), - SyscallSucceedsWithValue(sizeof(packet))); - EXPECT_EQ( - memcmp(kPayload, recv_buf + sizeof(struct iphdr) + sizeof(struct udphdr), - sizeof(kPayload)), - 0); - // The network stack should have set the source address. - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK); - struct iphdr iphdr = {}; - memcpy(&iphdr, recv_buf, sizeof(iphdr)); - EXPECT_NE(iphdr.id, 0); - - // Also verify that the packet we just sent was not delivered to the - // IPPROTO_RAW socket. - { - char recv_buf[sizeof(packet)]; - struct sockaddr_in src; - socklen_t src_size = sizeof(src); - ASSERT_THAT(recvfrom(socket_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT, - reinterpret_cast<struct sockaddr*>(&src), &src_size), - SyscallFailsWithErrno(EAGAIN)); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc deleted file mode 100644 index bd779da92..000000000 --- a/test/syscalls/linux/raw_socket_icmp.cc +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/capability.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <cstdint> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// The size of an empty ICMP packet and IP header together. -constexpr size_t kEmptyICMPSize = 28; - -// ICMP raw sockets get their own special tests because Linux automatically -// responds to ICMP echo requests, and thus a single echo request sent via -// loopback leads to 2 received ICMP packets. - -class RawSocketICMPTest : public ::testing::Test { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Checks that both an ICMP echo request and reply are received. Calls should - // be wrapped in ASSERT_NO_FATAL_FAILURE. - void ExpectICMPSuccess(const struct icmphdr& icmp); - - // Sends icmp via s_. - void SendEmptyICMP(const struct icmphdr& icmp); - - // Sends icmp via s_ to the given address. - void SendEmptyICMPTo(int sock, const struct sockaddr_in& addr, - const struct icmphdr& icmp); - - // Reads from s_ into recv_buf. - void ReceiveICMP(char* recv_buf, size_t recv_buf_len, size_t expected_size, - struct sockaddr_in* src); - - // Reads from sock into recv_buf. - void ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len, - size_t expected_size, struct sockaddr_in* src, int sock); - - // The socket used for both reading and writing. - int s_; - - // The loopback address. - struct sockaddr_in addr_; -}; - -void RawSocketICMPTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallSucceeds()); - - addr_ = {}; - - // "On raw sockets sin_port is set to the IP protocol." - ip(7). - addr_.sin_port = IPPROTO_IP; - addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr_.sin_family = AF_INET; -} - -void RawSocketICMPTest::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(s_), SyscallSucceeds()); - } -} - -// We'll only read an echo in this case, as the kernel won't respond to the -// malformed ICMP checksum. -TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, - // and ID. None of that should matter for raw sockets - the kernel should - // still give us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2012; - icmp.un.echo.id = 2014; - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - - // Veryify that we get the echo, then that there's nothing else to read. - char recv_buf[kEmptyICMPSize]; - struct sockaddr_in src; - ASSERT_NO_FATAL_FAILURE( - ReceiveICMP(recv_buf, sizeof(recv_buf), sizeof(struct icmphdr), &src)); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0); - // The packet should be identical to what we sent. - EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)), 0); - - // And there should be nothing left to read. - EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Send and receive an ICMP packet. -TEST_F(RawSocketICMPTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID. - // None of that should matter for raw sockets - the kernel should still give - // us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2012; - icmp.un.echo.id = 2014; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - - ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); -} - -// We should be able to create multiple raw sockets for the same protocol and -// receive the same packet on both. -TEST_F(RawSocketICMPTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor s2 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)); - - // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID. - // None of that should matter for raw sockets - the kernel should still give - // us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2016; - icmp.un.echo.id = 2018; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - - // Both sockets will receive the echo request and reply in indeterminate - // order, so we'll need to read 2 packets from each. - - // Receive on socket 1. - constexpr int kBufSize = kEmptyICMPSize; - char recv_buf1[2][kBufSize]; - struct sockaddr_in src; - for (int i = 0; i < 2; i++) { - ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf1[i], - ABSL_ARRAYSIZE(recv_buf1[i]), - sizeof(struct icmphdr), &src)); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0); - } - - // Receive on socket 2. - char recv_buf2[2][kBufSize]; - for (int i = 0; i < 2; i++) { - ASSERT_NO_FATAL_FAILURE( - ReceiveICMPFrom(recv_buf2[i], ABSL_ARRAYSIZE(recv_buf2[i]), - sizeof(struct icmphdr), &src, s2.get())); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0); - } - - // Ensure both sockets receive identical packets. - int types[] = {ICMP_ECHO, ICMP_ECHOREPLY}; - for (int type : types) { - auto match_type = [=](char buf[kBufSize]) { - struct icmphdr* icmp = - reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr)); - return icmp->type == type; - }; - auto icmp1_it = - std::find_if(std::begin(recv_buf1), std::end(recv_buf1), match_type); - auto icmp2_it = - std::find_if(std::begin(recv_buf2), std::end(recv_buf2), match_type); - ASSERT_NE(icmp1_it, std::end(recv_buf1)); - ASSERT_NE(icmp2_it, std::end(recv_buf2)); - EXPECT_EQ(memcmp(*icmp1_it + sizeof(struct iphdr), - *icmp2_it + sizeof(struct iphdr), sizeof(icmp)), - 0); - } -} - -// A raw ICMP socket and ping socket should both receive the ICMP packets -// intended for the ping socket. -TEST_F(RawSocketICMPTest, RawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor ping_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); - - // Ping sockets take care of the ICMP ID and checksum. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.un.echo.sequence = *static_cast<unsigned short*>(&icmp.un.echo.sequence); - ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, sizeof(icmp), 0, - reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceedsWithValue(sizeof(icmp))); - - // Receive on socket 1, which receives the echo request and reply in - // indeterminate order. - constexpr int kBufSize = kEmptyICMPSize; - char recv_buf1[2][kBufSize]; - struct sockaddr_in src; - for (int i = 0; i < 2; i++) { - ASSERT_NO_FATAL_FAILURE( - ReceiveICMP(recv_buf1[i], kBufSize, sizeof(struct icmphdr), &src)); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0); - } - - // Receive on socket 2. Ping sockets only get the echo reply, not the initial - // echo. - char ping_recv_buf[kBufSize]; - ASSERT_THAT(RetryEINTR(recv)(ping_sock.get(), ping_recv_buf, kBufSize, 0), - SyscallSucceedsWithValue(sizeof(struct icmphdr))); - - // Ensure both sockets receive identical echo reply packets. - auto match_type_raw = [=](char buf[kBufSize]) { - struct icmphdr* icmp = - reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr)); - return icmp->type == ICMP_ECHOREPLY; - }; - auto raw_reply_it = - std::find_if(std::begin(recv_buf1), std::end(recv_buf1), match_type_raw); - ASSERT_NE(raw_reply_it, std::end(recv_buf1)); - EXPECT_EQ( - memcmp(*raw_reply_it + sizeof(struct iphdr), ping_recv_buf, sizeof(icmp)), - 0); -} - -// A raw ICMP socket should be able to send a malformed short ICMP Echo Request, -// while ping socket should not. -// Neither should be able to receieve a short malformed packet. -TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor ping_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); - - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.un.echo.sequence = 0; - icmp.un.echo.id = 6789; - icmp.checksum = 0; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - - // Omit 2 bytes from ICMP packet. - constexpr int kShortICMPSize = sizeof(icmp) - 2; - - // Sending a malformed short ICMP message to a ping socket should fail. - ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, kShortICMPSize, 0, - reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallFailsWithErrno(EINVAL)); - - // Sending a malformed short ICMP message to a raw socket should not fail. - ASSERT_THAT(RetryEINTR(sendto)(s_, &icmp, kShortICMPSize, 0, - reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceedsWithValue(kShortICMPSize)); - - // Neither Ping nor Raw socket should have anything to read. - char recv_buf[kEmptyICMPSize]; - EXPECT_THAT(RetryEINTR(recv)(ping_sock.get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); - EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// A raw ICMP socket should be able to send a malformed short ICMP Echo Reply, -// while ping socket should not. -// Neither should be able to receieve a short malformed packet. -TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - FileDescriptor ping_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); - - struct icmphdr icmp; - icmp.type = ICMP_ECHOREPLY; - icmp.code = 0; - icmp.un.echo.sequence = 0; - icmp.un.echo.id = 6789; - icmp.checksum = 0; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - - // Omit 2 bytes from ICMP packet. - constexpr int kShortICMPSize = sizeof(icmp) - 2; - - // Sending a malformed short ICMP message to a ping socket should fail. - ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, kShortICMPSize, 0, - reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallFailsWithErrno(EINVAL)); - - // Sending a malformed short ICMP message to a raw socket should not fail. - ASSERT_THAT(RetryEINTR(sendto)(s_, &icmp, kShortICMPSize, 0, - reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)), - SyscallSucceedsWithValue(kShortICMPSize)); - - // Neither Ping nor Raw socket should have anything to read. - char recv_buf[kEmptyICMPSize]; - EXPECT_THAT(RetryEINTR(recv)(ping_sock.get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); - EXPECT_THAT(RetryEINTR(recv)(s_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Test that connect() sends packets to the right place. -TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), - SyscallSucceeds()); - - // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID. - // None of that should matter for raw sockets - the kernel should still give - // us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2003; - icmp.un.echo.id = 2004; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - ASSERT_THAT(send(s_, &icmp, sizeof(icmp), 0), - SyscallSucceedsWithValue(sizeof(icmp))); - - ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); -} - -// Bind to localhost, then send and receive packets. -TEST_F(RawSocketICMPTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), - SyscallSucceeds()); - - // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, - // and ID. None of that should matter for raw sockets - the kernel should - // still give us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2004; - icmp.un.echo.id = 2007; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - - ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); -} - -// Bind and connect to localhost and send/receive packets. -TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), - SyscallSucceeds()); - ASSERT_THAT( - connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), - SyscallSucceeds()); - - // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, - // and ID. None of that should matter for raw sockets - the kernel should - // still give us the packet. - struct icmphdr icmp; - icmp.type = ICMP_ECHO; - icmp.code = 0; - icmp.checksum = 0; - icmp.un.echo.sequence = 2010; - icmp.un.echo.id = 7; - icmp.checksum = ICMPChecksum(icmp, NULL, 0); - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - - ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); -} - -// Set and get SO_LINGER. -TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int level = SOL_SOCKET; - int type = SO_LINGER; - - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT(setsockopt(s_, level, type, &sl, sizeof(sl)), - SyscallSucceedsWithValue(0)); - - struct linger got_linger = {}; - socklen_t length = sizeof(sl); - ASSERT_THAT(getsockopt(s_, level, type, &got_linger, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); -} - -// Test getsockopt for SO_ACCEPTCONN. -TEST_F(RawSocketICMPTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); - - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} - -void RawSocketICMPTest::ExpectICMPSuccess(const struct icmphdr& icmp) { - // We're going to receive both the echo request and reply, but the order is - // indeterminate. - char recv_buf[kEmptyICMPSize]; - struct sockaddr_in src; - bool received_request = false; - bool received_reply = false; - - for (int i = 0; i < 2; i++) { - // Receive the packet. - ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf, ABSL_ARRAYSIZE(recv_buf), - sizeof(struct icmphdr), &src)); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(src)), 0); - struct icmphdr* recvd_icmp = - reinterpret_cast<struct icmphdr*>(recv_buf + sizeof(struct iphdr)); - switch (recvd_icmp->type) { - case ICMP_ECHO: - EXPECT_FALSE(received_request); - received_request = true; - // The packet should be identical to what we sent. - EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)), - 0); - break; - - case ICMP_ECHOREPLY: - EXPECT_FALSE(received_reply); - received_reply = true; - // Most fields should be the same. - EXPECT_EQ(recvd_icmp->code, icmp.code); - EXPECT_EQ(recvd_icmp->un.echo.sequence, icmp.un.echo.sequence); - EXPECT_EQ(recvd_icmp->un.echo.id, icmp.un.echo.id); - // A couple are different. - EXPECT_EQ(recvd_icmp->type, ICMP_ECHOREPLY); - // The checksum computed over the reply should still be valid. - EXPECT_EQ(ICMPChecksum(*recvd_icmp, NULL, 0), 0); - break; - } - } - - ASSERT_TRUE(received_request); - ASSERT_TRUE(received_reply); -} - -void RawSocketICMPTest::SendEmptyICMP(const struct icmphdr& icmp) { - ASSERT_NO_FATAL_FAILURE(SendEmptyICMPTo(s_, addr_, icmp)); -} - -void RawSocketICMPTest::SendEmptyICMPTo(int sock, - const struct sockaddr_in& addr, - const struct icmphdr& icmp) { - // It's safe to use const_cast here because sendmsg won't modify the iovec or - // address. - struct iovec iov = {}; - iov.iov_base = static_cast<void*>(const_cast<struct icmphdr*>(&icmp)); - iov.iov_len = sizeof(icmp); - struct msghdr msg = {}; - msg.msg_name = static_cast<void*>(const_cast<struct sockaddr_in*>(&addr)); - msg.msg_namelen = sizeof(addr); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(sizeof(icmp))); -} - -void RawSocketICMPTest::ReceiveICMP(char* recv_buf, size_t recv_buf_len, - size_t expected_size, - struct sockaddr_in* src) { - ASSERT_NO_FATAL_FAILURE( - ReceiveICMPFrom(recv_buf, recv_buf_len, expected_size, src, s_)); -} - -void RawSocketICMPTest::ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len, - size_t expected_size, - struct sockaddr_in* src, int sock) { - struct iovec iov = {}; - iov.iov_base = recv_buf; - iov.iov_len = recv_buf_len; - struct msghdr msg = {}; - msg.msg_name = src; - msg.msg_namelen = sizeof(*src); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - // We should receive the ICMP packet plus 20 bytes of IP header. - ASSERT_THAT(recvmsg(sock, &msg, 0), - SyscallSucceedsWithValue(expected_size + sizeof(struct iphdr))); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc deleted file mode 100644 index 087262535..000000000 --- a/test/syscalls/linux/read.cc +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/mman.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReadTest : public ::testing::Test { - void SetUp() override { - name_ = NewTempAbsPath(); - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_CREAT, 0644), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - } - - void TearDown() override { unlink(name_.c_str()); } - - public: - std::string name_; -}; - -TEST_F(ReadTest, ZeroBuffer) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds()); - - char msg[] = "hello world"; - EXPECT_THAT(PwriteFd(fd, msg, strlen(msg), 0), - SyscallSucceedsWithValue(strlen(msg))); - - char buf[10]; - EXPECT_THAT(ReadFd(fd, buf, 0), SyscallSucceedsWithValue(0)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(ReadTest, EmptyFileReturnsZeroAtEOF) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds()); - - char eof_buf[10]; - EXPECT_THAT(ReadFd(fd, eof_buf, 10), SyscallSucceedsWithValue(0)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(ReadTest, EofAfterRead) { - int fd; - ASSERT_THAT(fd = open(name_.c_str(), O_RDWR), SyscallSucceeds()); - - // Write some bytes to be read. - constexpr char kMessage[] = "hello world"; - EXPECT_THAT(PwriteFd(fd, kMessage, sizeof(kMessage), 0), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Read all of the bytes at once. - char buf[sizeof(kMessage)]; - EXPECT_THAT(ReadFd(fd, buf, sizeof(kMessage)), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Read again with a non-zero buffer and expect EOF. - char eof_buf[10]; - EXPECT_THAT(ReadFd(fd, eof_buf, 10), SyscallSucceedsWithValue(0)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(ReadTest, DevNullReturnsEof) { - int fd; - ASSERT_THAT(fd = open("/dev/null", O_RDONLY), SyscallSucceeds()); - std::vector<char> buf(1); - EXPECT_THAT(ReadFd(fd, buf.data(), 1), SyscallSucceedsWithValue(0)); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -const int kReadSize = 128 * 1024; - -// Do not allow random save as it could lead to partial reads. -TEST_F(ReadTest, CanReadFullyFromDevZero_NoRandomSave) { - int fd; - ASSERT_THAT(fd = open("/dev/zero", O_RDONLY), SyscallSucceeds()); - - std::vector<char> buf(kReadSize, 1); - EXPECT_THAT(ReadFd(fd, buf.data(), kReadSize), - SyscallSucceedsWithValue(kReadSize)); - EXPECT_THAT(close(fd), SyscallSucceeds()); - EXPECT_EQ(std::vector<char>(kReadSize, 0), buf); -} - -TEST_F(ReadTest, ReadDirectoryFails) { - const FileDescriptor file = - ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); - std::vector<char> buf(1); - EXPECT_THAT(ReadFd(file.get(), buf.data(), 1), SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(ReadTest, ReadWithOpath) { - 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_PATH)); - std::vector<char> buf(1); - EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallFailsWithErrno(EBADF)); -} - -// Test that partial writes that hit SIGSEGV are correctly handled and return -// partial write. -TEST_F(ReadTest, PartialReadSIGSEGV) { - // Allocate 2 pages and remove permission from the second. - const size_t size = 2 * kPageSize; - void* addr = - mmap(0, size, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - ASSERT_NE(addr, MAP_FAILED); - auto cleanup = Cleanup( - [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(name_.c_str(), O_RDWR, 0666)); - for (size_t i = 0; i < 2; i++) { - EXPECT_THAT(pwrite(fd.get(), addr, size, 0), - SyscallSucceedsWithValue(size)); - } - - void* badAddr = reinterpret_cast<char*>(addr) + kPageSize; - ASSERT_THAT(mprotect(badAddr, kPageSize, PROT_NONE), SyscallSucceeds()); - - // Attempt to read to both pages. Create a non-contiguous iovec pair to - // ensure operation is done in 2 steps. - struct iovec iov[] = { - { - .iov_base = addr, - .iov_len = kPageSize, - }, - { - .iov_base = addr, - .iov_len = size, - }, - }; - EXPECT_THAT(preadv(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), - SyscallSucceedsWithValue(size)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/readahead.cc b/test/syscalls/linux/readahead.cc deleted file mode 100644 index 71073bb3c..000000000 --- a/test/syscalls/linux/readahead.cc +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ReadaheadTest, InvalidFD) { - EXPECT_THAT(readahead(-1, 1, 1), SyscallFailsWithErrno(EBADF)); -} - -TEST(ReadaheadTest, UnsupportedFile) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, 0)); - ASSERT_THAT(readahead(sock.get(), 1, 1), SyscallFailsWithErrno(EINVAL)); -} - -TEST(ReadaheadTest, InvalidOffset) { - // This test is not valid for some Linux Kernels. - SKIP_IF(!IsRunningOnGvisor()); - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - EXPECT_THAT(readahead(fd.get(), -1, 1), SyscallFailsWithErrno(EINVAL)); -} - -TEST(ReadaheadTest, ValidOffset) { - constexpr char kData[] = "123"; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // N.B. The implementation of readahead is filesystem-specific, and a file - // backed by ram may return EINVAL because there is nothing to be read. - EXPECT_THAT(readahead(fd.get(), 1, 1), AnyOf(SyscallSucceedsWithValue(0), - SyscallFailsWithErrno(EINVAL))); -} - -TEST(ReadaheadTest, PastEnd) { - constexpr char kData[] = "123"; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - // See above. - EXPECT_THAT(readahead(fd.get(), 2, 2), AnyOf(SyscallSucceedsWithValue(0), - SyscallFailsWithErrno(EINVAL))); -} - -TEST(ReadaheadTest, CrossesEnd) { - constexpr char kData[] = "123"; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - // See above. - EXPECT_THAT(readahead(fd.get(), 4, 2), AnyOf(SyscallSucceedsWithValue(0), - SyscallFailsWithErrno(EINVAL))); -} - -TEST(ReadaheadTest, WriteOnly) { - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_WRONLY)); - EXPECT_THAT(readahead(fd.get(), 0, 1), SyscallFailsWithErrno(EBADF)); -} - -TEST(ReadaheadTest, InvalidSize) { - // This test is not valid on some Linux kernels. - SKIP_IF(!IsRunningOnGvisor()); - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - EXPECT_THAT(readahead(fd.get(), 0, -1), SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/readv.cc b/test/syscalls/linux/readv.cc deleted file mode 100644 index 86808d255..000000000 --- a/test/syscalls/linux/readv.cc +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/file_base.h" -#include "test/syscalls/linux/readv_common.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReadvTest : public FileTest { - void SetUp() override { - FileTest::SetUp(); - - ASSERT_THAT(write(test_file_fd_.get(), kReadvTestData, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - ASSERT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(write(test_pipe_[1], kReadvTestData, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - } -}; - -TEST_F(ReadvTest, ReadOneBufferPerByte_File) { - ReadOneBufferPerByte(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadOneBufferPerByte_Pipe) { - ReadOneBufferPerByte(test_pipe_[0]); -} - -TEST_F(ReadvTest, ReadOneHalfAtATime_File) { - ReadOneHalfAtATime(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadOneHalfAtATime_Pipe) { - ReadOneHalfAtATime(test_pipe_[0]); -} - -TEST_F(ReadvTest, ReadAllOneBuffer_File) { - ReadAllOneBuffer(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadAllOneBuffer_Pipe) { ReadAllOneBuffer(test_pipe_[0]); } - -TEST_F(ReadvTest, ReadAllOneLargeBuffer_File) { - ReadAllOneLargeBuffer(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadAllOneLargeBuffer_Pipe) { - ReadAllOneLargeBuffer(test_pipe_[0]); -} - -TEST_F(ReadvTest, ReadBuffersOverlapping_File) { - ReadBuffersOverlapping(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadBuffersOverlapping_Pipe) { - ReadBuffersOverlapping(test_pipe_[0]); -} - -TEST_F(ReadvTest, ReadBuffersDiscontinuous_File) { - ReadBuffersDiscontinuous(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadBuffersDiscontinuous_Pipe) { - ReadBuffersDiscontinuous(test_pipe_[0]); -} - -TEST_F(ReadvTest, ReadIovecsCompletelyFilled_File) { - ReadIovecsCompletelyFilled(test_file_fd_.get()); -} - -TEST_F(ReadvTest, ReadIovecsCompletelyFilled_Pipe) { - ReadIovecsCompletelyFilled(test_pipe_[0]); -} - -TEST_F(ReadvTest, BadFileDescriptor) { - char buffer[1024]; - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = 1024; - - ASSERT_THAT(readv(-1, iov, 1024), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(ReadvTest, BadIovecsPointer_File) { - ASSERT_THAT(readv(test_file_fd_.get(), nullptr, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvTest, BadIovecsPointer_Pipe) { - ASSERT_THAT(readv(test_pipe_[0], nullptr, 1), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvTest, BadIovecBase_File) { - struct iovec iov[1]; - iov[0].iov_base = nullptr; - iov[0].iov_len = 1024; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvTest, BadIovecBase_Pipe) { - struct iovec iov[1]; - iov[0].iov_base = nullptr; - iov[0].iov_len = 1024; - ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvTest, ZeroIovecs_File) { - struct iovec iov[1]; - iov[0].iov_base = 0; - iov[0].iov_len = 0; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), SyscallSucceeds()); -} - -TEST_F(ReadvTest, ZeroIovecs_Pipe) { - struct iovec iov[1]; - iov[0].iov_base = 0; - iov[0].iov_len = 0; - ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallSucceeds()); -} - -TEST_F(ReadvTest, NotReadable_File) { - char buffer[1024]; - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = 1024; - - std::string wronly_file = NewTempAbsPath(); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(wronly_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR)); - ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EBADF)); - fd.reset(); // Close before unlinking. - ASSERT_THAT(unlink(wronly_file.c_str()), SyscallSucceeds()); -} - -TEST_F(ReadvTest, NotReadable_Pipe) { - char buffer[1024]; - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = 1024; - ASSERT_THAT(readv(test_pipe_[1], iov, 1), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(ReadvTest, DirNotReadable) { - char buffer[1024]; - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = 1024; - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); - ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EISDIR)); -} - -TEST_F(ReadvTest, OffsetIncremented) { - char* buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = kReadvTestDataSize; - - ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), - SyscallSucceedsWithValue(kReadvTestDataSize)); - ASSERT_THAT(lseek(test_file_fd_.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(kReadvTestDataSize)); - - free(buffer); -} - -TEST_F(ReadvTest, EndOfFile) { - char* buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), - SyscallSucceedsWithValue(kReadvTestDataSize)); - free(buffer); - - buffer = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - iov[0].iov_base = buffer; - iov[0].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 1), SyscallSucceedsWithValue(0)); - free(buffer); -} - -TEST_F(ReadvTest, WouldBlock_Pipe) { - struct iovec iov[1]; - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - iov[0].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_pipe_[0], iov, 1), - SyscallSucceedsWithValue(kReadvTestDataSize)); - free(iov[0].iov_base); - - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallFailsWithErrno(EAGAIN)); - free(iov[0].iov_base); -} - -TEST_F(ReadvTest, ZeroBuffer) { - char buf[10]; - struct iovec iov[1]; - iov[0].iov_base = buf; - iov[0].iov_len = 0; - ASSERT_THAT(readv(test_pipe_[0], iov, 1), SyscallSucceedsWithValue(0)); -} - -TEST_F(ReadvTest, NullIovecInNonemptyArray) { - std::vector<char> buf(kReadvTestDataSize); - struct iovec iov[2]; - iov[0].iov_base = nullptr; - iov[0].iov_len = 0; - iov[1].iov_base = buf.data(); - iov[1].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 2), - SyscallSucceedsWithValue(kReadvTestDataSize)); -} - -TEST_F(ReadvTest, IovecOutsideTaskAddressRangeInNonemptyArray) { - std::vector<char> buf(kReadvTestDataSize); - struct iovec iov[2]; - iov[0].iov_base = reinterpret_cast<void*>(~static_cast<uintptr_t>(0)); - iov[0].iov_len = 0; - iov[1].iov_base = buf.data(); - iov[1].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_file_fd_.get(), iov, 2), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvTest, ReadvWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - char buffer[1024]; - struct iovec iov[1]; - iov[0].iov_base = buffer; - iov[0].iov_len = 1024; - - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); - - ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EBADF)); -} - -// This test depends on the maximum extent of a single readv() syscall, so -// we can't tolerate interruption from saving. -TEST(ReadvTestNoFixture, TruncatedAtMax_NoRandomSave) { - // Ensure that we won't be interrupted by ITIMER_PROF. This is particularly - // important in environments where automated profiling tools may start - // ITIMER_PROF automatically. - struct itimerval itv = {}; - auto const cleanup_itimer = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_PROF, itv)); - - // From Linux's include/linux/fs.h. - size_t const MAX_RW_COUNT = INT_MAX & ~(kPageSize - 1); - - // Create an iovec array with 3 segments pointing to consecutive parts of a - // buffer. The first covers all but the last three pages, and should be - // written to in its entirety. The second covers the last page before - // MAX_RW_COUNT and the first page after; only the first page should be - // written to. The third covers the last page of the buffer, and should be - // skipped entirely. - size_t const kBufferSize = MAX_RW_COUNT + 2 * kPageSize; - size_t const kFirstOffset = MAX_RW_COUNT - kPageSize; - size_t const kSecondOffset = MAX_RW_COUNT + kPageSize; - // The buffer is too big to fit on the stack. - std::vector<char> buf(kBufferSize); - struct iovec iov[3]; - iov[0].iov_base = buf.data(); - iov[0].iov_len = kFirstOffset; - iov[1].iov_base = buf.data() + kFirstOffset; - iov[1].iov_len = kSecondOffset - kFirstOffset; - iov[2].iov_base = buf.data() + kSecondOffset; - iov[2].iov_len = kBufferSize - kSecondOffset; - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_RDONLY)); - EXPECT_THAT(readv(fd.get(), iov, 3), SyscallSucceedsWithValue(MAX_RW_COUNT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/readv_common.cc b/test/syscalls/linux/readv_common.cc deleted file mode 100644 index 2694dc64f..000000000 --- a/test/syscalls/linux/readv_common.cc +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// MatchesStringLength checks that a tuple argument of (struct iovec *, int) -// corresponding to an iovec array and its length, contains data that matches -// the string length strlen. -MATCHER_P(MatchesStringLength, strlen, "") { - struct iovec* iovs = arg.first; - int niov = arg.second; - int offset = 0; - for (int i = 0; i < niov; i++) { - offset += iovs[i].iov_len; - } - if (offset != static_cast<int>(strlen)) { - *result_listener << offset; - return false; - } - return true; -} - -// MatchesStringValue checks that a tuple argument of (struct iovec *, int) -// corresponding to an iovec array and its length, contains data that matches -// the string value str. -MATCHER_P(MatchesStringValue, str, "") { - struct iovec* iovs = arg.first; - int len = strlen(str); - int niov = arg.second; - int offset = 0; - for (int i = 0; i < niov; i++) { - struct iovec iov = iovs[i]; - if (len < offset) { - *result_listener << "strlen " << len << " < offset " << offset; - return false; - } - if (strncmp(static_cast<char*>(iov.iov_base), &str[offset], iov.iov_len)) { - absl::string_view iovec_string(static_cast<char*>(iov.iov_base), - iov.iov_len); - *result_listener << iovec_string << " @offset " << offset; - return false; - } - offset += iov.iov_len; - } - return true; -} - -extern const char kReadvTestData[] = - "127.0.0.1 localhost" - "" - "# The following lines are desirable for IPv6 capable hosts" - "::1 ip6-localhost ip6-loopback" - "fe00::0 ip6-localnet" - "ff00::0 ip6-mcastprefix" - "ff02::1 ip6-allnodes" - "ff02::2 ip6-allrouters" - "ff02::3 ip6-allhosts" - "192.168.1.100 a" - "93.184.216.34 foo.bar.example.com xcpu"; -extern const size_t kReadvTestDataSize = sizeof(kReadvTestData); - -static void ReadAllOneProvidedBuffer(int fd, std::vector<char>* buffer) { - struct iovec iovs[1]; - iovs[0].iov_base = buffer->data(); - iovs[0].iov_len = kReadvTestDataSize; - - ASSERT_THAT(readv(fd, iovs, 1), SyscallSucceedsWithValue(kReadvTestDataSize)); - - std::pair<struct iovec*, int> iovec_desc(iovs, 1); - EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize)); - EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData)); -} - -void ReadAllOneBuffer(int fd) { - std::vector<char> buffer(kReadvTestDataSize); - ReadAllOneProvidedBuffer(fd, &buffer); -} - -void ReadAllOneLargeBuffer(int fd) { - std::vector<char> buffer(10 * kReadvTestDataSize); - ReadAllOneProvidedBuffer(fd, &buffer); -} - -void ReadOneHalfAtATime(int fd) { - int len0 = kReadvTestDataSize / 2; - int len1 = kReadvTestDataSize - len0; - std::vector<char> buffer0(len0); - std::vector<char> buffer1(len1); - - struct iovec iovs[2]; - iovs[0].iov_base = buffer0.data(); - iovs[0].iov_len = len0; - iovs[1].iov_base = buffer1.data(); - iovs[1].iov_len = len1; - - ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(kReadvTestDataSize)); - - std::pair<struct iovec*, int> iovec_desc(iovs, 2); - EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize)); - EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData)); -} - -void ReadOneBufferPerByte(int fd) { - std::vector<char> buffer(kReadvTestDataSize); - std::vector<struct iovec> iovs(kReadvTestDataSize); - char* buffer_ptr = buffer.data(); - struct iovec* iovs_ptr = iovs.data(); - - for (int i = 0; i < static_cast<int>(kReadvTestDataSize); i++) { - struct iovec iov = { - .iov_base = &buffer_ptr[i], - .iov_len = 1, - }; - iovs_ptr[i] = iov; - } - - ASSERT_THAT(readv(fd, iovs_ptr, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - - std::pair<struct iovec*, int> iovec_desc(iovs.data(), kReadvTestDataSize); - EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize)); - EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData)); -} - -void ReadBuffersOverlapping(int fd) { - // overlap the first overlap_bytes. - int overlap_bytes = 8; - std::vector<char> buffer(kReadvTestDataSize); - - // overlapping causes us to get more data. - int expected_size = kReadvTestDataSize + overlap_bytes; - std::vector<char> expected(expected_size); - char* expected_ptr = expected.data(); - memcpy(expected_ptr, &kReadvTestData[overlap_bytes], overlap_bytes); - memcpy(&expected_ptr[overlap_bytes], &kReadvTestData[overlap_bytes], - kReadvTestDataSize - overlap_bytes); - - struct iovec iovs[2]; - iovs[0].iov_base = buffer.data(); - iovs[0].iov_len = overlap_bytes; - iovs[1].iov_base = buffer.data(); - iovs[1].iov_len = kReadvTestDataSize; - - ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(kReadvTestDataSize)); - - std::pair<struct iovec*, int> iovec_desc(iovs, 2); - EXPECT_THAT(iovec_desc, MatchesStringLength(expected_size)); - EXPECT_THAT(iovec_desc, MatchesStringValue(expected_ptr)); -} - -void ReadBuffersDiscontinuous(int fd) { - // Each iov is 1 byte separated by 1 byte. - std::vector<char> buffer(kReadvTestDataSize * 2); - std::vector<struct iovec> iovs(kReadvTestDataSize); - - char* buffer_ptr = buffer.data(); - struct iovec* iovs_ptr = iovs.data(); - - for (int i = 0; i < static_cast<int>(kReadvTestDataSize); i++) { - struct iovec iov = { - .iov_base = &buffer_ptr[i * 2], - .iov_len = 1, - }; - iovs_ptr[i] = iov; - } - - ASSERT_THAT(readv(fd, iovs_ptr, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - - std::pair<struct iovec*, int> iovec_desc(iovs.data(), kReadvTestDataSize); - EXPECT_THAT(iovec_desc, MatchesStringLength(kReadvTestDataSize)); - EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData)); -} - -void ReadIovecsCompletelyFilled(int fd) { - int half = kReadvTestDataSize / 2; - std::vector<char> buffer(kReadvTestDataSize); - char* buffer_ptr = buffer.data(); - memset(buffer.data(), '\0', kReadvTestDataSize); - - struct iovec iovs[2]; - iovs[0].iov_base = buffer.data(); - iovs[0].iov_len = half; - iovs[1].iov_base = &buffer_ptr[half]; - iovs[1].iov_len = half; - - ASSERT_THAT(readv(fd, iovs, 2), SyscallSucceedsWithValue(half * 2)); - - std::pair<struct iovec*, int> iovec_desc(iovs, 2); - EXPECT_THAT(iovec_desc, MatchesStringLength(half * 2)); - EXPECT_THAT(iovec_desc, MatchesStringValue(kReadvTestData)); - - char* str = static_cast<char*>(iovs[0].iov_base); - str[iovs[0].iov_len - 1] = '\0'; - ASSERT_EQ(half - 1, strlen(str)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/readv_common.h b/test/syscalls/linux/readv_common.h deleted file mode 100644 index 2fa40c35f..000000000 --- a/test/syscalls/linux/readv_common.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_READV_COMMON_H_ -#define GVISOR_TEST_SYSCALLS_READV_COMMON_H_ - -#include <stddef.h> - -namespace gvisor { -namespace testing { - -// A NUL-terminated string containing the data used by tests using the following -// test helpers. -extern const char kReadvTestData[]; - -// The size of kReadvTestData, including the terminating NUL. -extern const size_t kReadvTestDataSize; - -// ReadAllOneBuffer asserts that it can read kReadvTestData from an fd using -// exactly one iovec. -void ReadAllOneBuffer(int fd); - -// ReadAllOneLargeBuffer asserts that it can read kReadvTestData from an fd -// using exactly one iovec containing an overly large buffer. -void ReadAllOneLargeBuffer(int fd); - -// ReadOneHalfAtATime asserts that it can read test_data_from an fd using -// exactly two iovecs that are roughly equivalent in size. -void ReadOneHalfAtATime(int fd); - -// ReadOneBufferPerByte asserts that it can read kReadvTestData from an fd -// using one iovec per byte. -void ReadOneBufferPerByte(int fd); - -// ReadBuffersOverlapping asserts that it can read kReadvTestData from an fd -// where two iovecs are overlapping. -void ReadBuffersOverlapping(int fd); - -// ReadBuffersDiscontinuous asserts that it can read kReadvTestData from an fd -// where each iovec is discontinuous from the next by 1 byte. -void ReadBuffersDiscontinuous(int fd); - -// ReadIovecsCompletelyFilled asserts that the previous iovec is completely -// filled before moving onto the next. -void ReadIovecsCompletelyFilled(int fd); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_READV_COMMON_H_ diff --git a/test/syscalls/linux/readv_socket.cc b/test/syscalls/linux/readv_socket.cc deleted file mode 100644 index dd6fb7008..000000000 --- a/test/syscalls/linux/readv_socket.cc +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/readv_common.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class ReadvSocketTest : public ::testing::Test { - public: - void SetUp() override { - test_unix_stream_socket_[0] = -1; - test_unix_stream_socket_[1] = -1; - test_unix_dgram_socket_[0] = -1; - test_unix_dgram_socket_[1] = -1; - test_unix_seqpacket_socket_[0] = -1; - test_unix_seqpacket_socket_[1] = -1; - - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, test_unix_stream_socket_), - SyscallSucceeds()); - ASSERT_THAT(fcntl(test_unix_stream_socket_[0], F_SETFL, O_NONBLOCK), - SyscallSucceeds()); - ASSERT_THAT(socketpair(AF_UNIX, SOCK_DGRAM, 0, test_unix_dgram_socket_), - SyscallSucceeds()); - ASSERT_THAT(fcntl(test_unix_dgram_socket_[0], F_SETFL, O_NONBLOCK), - SyscallSucceeds()); - ASSERT_THAT( - socketpair(AF_UNIX, SOCK_SEQPACKET, 0, test_unix_seqpacket_socket_), - SyscallSucceeds()); - ASSERT_THAT(fcntl(test_unix_seqpacket_socket_[0], F_SETFL, O_NONBLOCK), - SyscallSucceeds()); - - ASSERT_THAT( - write(test_unix_stream_socket_[1], kReadvTestData, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - ASSERT_THAT( - write(test_unix_dgram_socket_[1], kReadvTestData, kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - ASSERT_THAT(write(test_unix_seqpacket_socket_[1], kReadvTestData, - kReadvTestDataSize), - SyscallSucceedsWithValue(kReadvTestDataSize)); - } - - void TearDown() override { - close(test_unix_stream_socket_[0]); - close(test_unix_stream_socket_[1]); - - close(test_unix_dgram_socket_[0]); - close(test_unix_dgram_socket_[1]); - - close(test_unix_seqpacket_socket_[0]); - close(test_unix_seqpacket_socket_[1]); - } - - int test_unix_stream_socket_[2]; - int test_unix_dgram_socket_[2]; - int test_unix_seqpacket_socket_[2]; -}; - -TEST_F(ReadvSocketTest, ReadOneBufferPerByte_StreamSocket) { - ReadOneBufferPerByte(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadOneBufferPerByte_DgramSocket) { - ReadOneBufferPerByte(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadOneBufferPerByte_SeqPacketSocket) { - ReadOneBufferPerByte(test_unix_seqpacket_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadOneHalfAtATime_StreamSocket) { - ReadOneHalfAtATime(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadOneHalfAtATime_DgramSocket) { - ReadOneHalfAtATime(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadAllOneBuffer_StreamSocket) { - ReadAllOneBuffer(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadAllOneBuffer_DgramSocket) { - ReadAllOneBuffer(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadAllOneLargeBuffer_StreamSocket) { - ReadAllOneLargeBuffer(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadAllOneLargeBuffer_DgramSocket) { - ReadAllOneLargeBuffer(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadBuffersOverlapping_StreamSocket) { - ReadBuffersOverlapping(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadBuffersOverlapping_DgramSocket) { - ReadBuffersOverlapping(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadBuffersDiscontinuous_StreamSocket) { - ReadBuffersDiscontinuous(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadBuffersDiscontinuous_DgramSocket) { - ReadBuffersDiscontinuous(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadIovecsCompletelyFilled_StreamSocket) { - ReadIovecsCompletelyFilled(test_unix_stream_socket_[0]); -} - -TEST_F(ReadvSocketTest, ReadIovecsCompletelyFilled_DgramSocket) { - ReadIovecsCompletelyFilled(test_unix_dgram_socket_[0]); -} - -TEST_F(ReadvSocketTest, BadIovecsPointer_StreamSocket) { - ASSERT_THAT(readv(test_unix_stream_socket_[0], nullptr, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvSocketTest, BadIovecsPointer_DgramSocket) { - ASSERT_THAT(readv(test_unix_dgram_socket_[0], nullptr, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvSocketTest, BadIovecBase_StreamSocket) { - struct iovec iov[1]; - iov[0].iov_base = nullptr; - iov[0].iov_len = 1024; - ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvSocketTest, BadIovecBase_DgramSocket) { - struct iovec iov[1]; - iov[0].iov_base = nullptr; - iov[0].iov_len = 1024; - ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(ReadvSocketTest, ZeroIovecs_StreamSocket) { - struct iovec iov[1]; - iov[0].iov_base = 0; - iov[0].iov_len = 0; - ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1), SyscallSucceeds()); -} - -TEST_F(ReadvSocketTest, ZeroIovecs_DgramSocket) { - struct iovec iov[1]; - iov[0].iov_base = 0; - iov[0].iov_len = 0; - ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1), SyscallSucceeds()); -} - -TEST_F(ReadvSocketTest, WouldBlock_StreamSocket) { - struct iovec iov[1]; - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - iov[0].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1), - SyscallSucceedsWithValue(kReadvTestDataSize)); - free(iov[0].iov_base); - - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - ASSERT_THAT(readv(test_unix_stream_socket_[0], iov, 1), - SyscallFailsWithErrno(EAGAIN)); - free(iov[0].iov_base); -} - -TEST_F(ReadvSocketTest, WouldBlock_DgramSocket) { - struct iovec iov[1]; - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - iov[0].iov_len = kReadvTestDataSize; - ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1), - SyscallSucceedsWithValue(kReadvTestDataSize)); - free(iov[0].iov_base); - - iov[0].iov_base = reinterpret_cast<char*>(malloc(kReadvTestDataSize)); - ASSERT_THAT(readv(test_unix_dgram_socket_[0], iov, 1), - SyscallFailsWithErrno(EAGAIN)); - free(iov[0].iov_base); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc deleted file mode 100644 index b1a813de0..000000000 --- a/test/syscalls/linux/rename.cc +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <stdio.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(RenameTest, RootToAnything) { - ASSERT_THAT(rename("/", "/bin"), SyscallFailsWithErrno(EBUSY)); -} - -TEST(RenameTest, AnythingToRoot) { - ASSERT_THAT(rename("/bin", "/"), SyscallFailsWithErrno(EBUSY)); -} - -TEST(RenameTest, SourceIsAncestorOfTarget) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto subdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - ASSERT_THAT(rename(dir.path().c_str(), subdir.path().c_str()), - SyscallFailsWithErrno(EINVAL)); - - // Try an even deeper directory. - auto deep_subdir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(subdir.path())); - ASSERT_THAT(rename(dir.path().c_str(), deep_subdir.path().c_str()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(RenameTest, TargetIsAncestorOfSource) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto subdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - ASSERT_THAT(rename(subdir.path().c_str(), dir.path().c_str()), - SyscallFailsWithErrno(ENOTEMPTY)); - - // Try an even deeper directory. - auto deep_subdir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(subdir.path())); - ASSERT_THAT(rename(deep_subdir.path().c_str(), dir.path().c_str()), - SyscallFailsWithErrno(ENOTEMPTY)); -} - -TEST(RenameTest, FileToSelf) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - EXPECT_THAT(rename(f.path().c_str(), f.path().c_str()), SyscallSucceeds()); -} - -TEST(RenameTest, DirectoryToSelf) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(rename(f.path().c_str(), f.path().c_str()), SyscallSucceeds()); -} - -TEST(RenameTest, FileToSameDirectory) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - std::string const newpath = NewTempAbsPath(); - ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = f.release(); - f.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, DirectoryToSameDirectory) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string const newpath = NewTempAbsPath(); - ASSERT_THAT(rename(dir.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = dir.release(); - dir.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, FileToParentDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path())); - std::string const newpath = NewTempAbsPathInDir(dir1.path()); - ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = f.release(); - f.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, DirectoryToParentDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - auto dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir2.path())); - EXPECT_THAT(IsDirectory(dir3.path()), IsPosixErrorOkAndHolds(true)); - std::string const newpath = NewTempAbsPathInDir(dir1.path()); - ASSERT_THAT(rename(dir3.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = dir3.release(); - dir3.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); - EXPECT_THAT(IsDirectory(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, FileToChildDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - std::string const newpath = NewTempAbsPathInDir(dir2.path()); - ASSERT_THAT(rename(f.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = f.release(); - f.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, DirectoryToChildDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - auto dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - std::string const newpath = NewTempAbsPathInDir(dir2.path()); - ASSERT_THAT(rename(dir3.path().c_str(), newpath.c_str()), SyscallSucceeds()); - std::string const oldpath = dir3.release(); - dir3.reset(newpath); - EXPECT_THAT(Exists(oldpath), IsPosixErrorOkAndHolds(false)); - EXPECT_THAT(Exists(newpath), IsPosixErrorOkAndHolds(true)); - EXPECT_THAT(IsDirectory(newpath), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, DirectoryToOwnChildDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); - std::string const newpath = NewTempAbsPathInDir(dir2.path()); - ASSERT_THAT(rename(dir1.path().c_str(), newpath.c_str()), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(RenameTest, FileOverwritesFile) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir.path(), "first", TempPath::kDefaultFileMode)); - auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir.path(), "second", TempPath::kDefaultFileMode)); - ASSERT_THAT(rename(f1.path().c_str(), f2.path().c_str()), SyscallSucceeds()); - EXPECT_THAT(Exists(f1.path()), IsPosixErrorOkAndHolds(false)); - - f1.release(); - std::string f2_contents; - ASSERT_NO_ERRNO(GetContents(f2.path(), &f2_contents)); - EXPECT_EQ("first", f2_contents); -} - -TEST(RenameTest, DirectoryOverwritesDirectoryLinkCount) { - // Directory link counts are synthetic on overlay filesystems. - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); - - auto parent1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(2)); - - auto parent2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(2)); - - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent1.path())); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent2.path())); - - EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(3)); - EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(3)); - - ASSERT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()), - SyscallSucceeds()); - - EXPECT_THAT(Links(parent1.path()), IsPosixErrorOkAndHolds(2)); - EXPECT_THAT(Links(parent2.path()), IsPosixErrorOkAndHolds(3)); -} - -TEST(RenameTest, FileDoesNotExist) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string source = JoinPath(dir.path(), "source"); - const std::string dest = JoinPath(dir.path(), "dest"); - ASSERT_THAT(rename(source.c_str(), dest.c_str()), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(RenameTest, FileDoesNotOverwriteDirectory) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(rename(f.path().c_str(), dir.path().c_str()), - SyscallFailsWithErrno(EISDIR)); -} - -TEST(RenameTest, DirectoryDoesNotOverwriteFile) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(rename(dir.path().c_str(), f.path().c_str()), - SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(RenameTest, DirectoryOverwritesEmptyDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()), - SyscallSucceeds()); - EXPECT_THAT(Exists(dir1.path()), IsPosixErrorOkAndHolds(false)); - dir1.release(); - EXPECT_THAT(Exists(JoinPath(dir2.path(), Basename(f.path()))), - IsPosixErrorOkAndHolds(true)); - f.release(); -} - -TEST(RenameTest, FailsWithDots) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto dir1_dot = absl::StrCat(dir1.path(), "/."); - auto dir2_dot = absl::StrCat(dir2.path(), "/."); - auto dir1_dot_dot = absl::StrCat(dir1.path(), "/.."); - auto dir2_dot_dot = absl::StrCat(dir2.path(), "/.."); - - // Try with dot paths in the first argument - EXPECT_THAT(rename(dir1_dot.c_str(), dir2.path().c_str()), - SyscallFailsWithErrno(EBUSY)); - EXPECT_THAT(rename(dir1_dot_dot.c_str(), dir2.path().c_str()), - SyscallFailsWithErrno(EBUSY)); - - // Try with dot paths in the second argument - EXPECT_THAT(rename(dir1.path().c_str(), dir2_dot.c_str()), - SyscallFailsWithErrno(EBUSY)); - EXPECT_THAT(rename(dir1.path().c_str(), dir2_dot_dot.c_str()), - SyscallFailsWithErrno(EBUSY)); -} - -TEST(RenameTest, DirectoryDoesNotOverwriteNonemptyDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path())); - ASSERT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()), - SyscallFailsWithErrno(ENOTEMPTY)); -} - -TEST(RenameTest, FailsWhenOldParentNotWritable) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - // dir1 is not writable. - ASSERT_THAT(chmod(dir1.path().c_str(), 0555), SyscallSucceeds()); - - std::string const newpath = NewTempAbsPathInDir(dir2.path()); - EXPECT_THAT(rename(f1.path().c_str(), newpath.c_str()), - SyscallFailsWithErrno(EACCES)); -} - -TEST(RenameTest, FailsWhenNewParentNotWritable) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - // dir2 is not writable. - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0555)); - - std::string const newpath = NewTempAbsPathInDir(dir2.path()); - EXPECT_THAT(rename(f1.path().c_str(), newpath.c_str()), - SyscallFailsWithErrno(EACCES)); -} - -// Equivalent to FailsWhenNewParentNotWritable, but with a destination file -// to overwrite. -TEST(RenameTest, OverwriteFailsWhenNewParentNotWritable) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - - // dir2 is not writable. - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir2.path())); - ASSERT_THAT(chmod(dir2.path().c_str(), 0555), SyscallSucceeds()); - - EXPECT_THAT(rename(f1.path().c_str(), f2.path().c_str()), - SyscallFailsWithErrno(EACCES)); -} - -// If the parent directory of source is not accessible, rename returns EACCES -// because the user cannot determine if source exists. -TEST(RenameTest, FileDoesNotExistWhenNewParentNotExecutable) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - // No execute permission. - auto dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0400)); - - const std::string source = JoinPath(dir.path(), "source"); - const std::string dest = JoinPath(dir.path(), "dest"); - ASSERT_THAT(rename(source.c_str(), dest.c_str()), - SyscallFailsWithErrno(EACCES)); -} - -TEST(RenameTest, DirectoryWithOpenFdOverwritesEmptyDirectory) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path())); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Get an fd on dir1 - int fd; - ASSERT_THAT(fd = open(dir1.path().c_str(), O_DIRECTORY), SyscallSucceeds()); - auto close_f = Cleanup([fd] { - // Close the fd on f. - EXPECT_THAT(close(fd), SyscallSucceeds()); - }); - - EXPECT_THAT(rename(dir1.path().c_str(), dir2.path().c_str()), - SyscallSucceeds()); - - const std::string new_f_path = JoinPath(dir2.path(), Basename(f.path())); - - auto remove_f = Cleanup([&] { - // Delete f in its new location. - ASSERT_NO_ERRNO(Delete(new_f_path)); - f.release(); - }); - - EXPECT_THAT(Exists(dir1.path()), IsPosixErrorOkAndHolds(false)); - dir1.release(); - EXPECT_THAT(Exists(new_f_path), IsPosixErrorOkAndHolds(true)); -} - -TEST(RenameTest, FileWithOpenFd) { - TempPath root_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath dir1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); - TempPath dir2 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); - TempPath dir3 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); - - // Create file in dir1. - constexpr char kContents[] = "foo"; - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir1.path(), kContents, TempPath::kDefaultFileMode)); - - // Get fd on file. - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - - // Move f to dir2. - const std::string path2 = NewTempAbsPathInDir(dir2.path()); - ASSERT_THAT(rename(f.path().c_str(), path2.c_str()), SyscallSucceeds()); - - // Read f's kContents. - char buf[sizeof(kContents)]; - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(kContents), 0), - SyscallSucceedsWithValue(sizeof(kContents) - 1)); - EXPECT_EQ(absl::string_view(buf, sizeof(buf) - 1), kContents); - - // Move f to dir3. - const std::string path3 = NewTempAbsPathInDir(dir3.path()); - ASSERT_THAT(rename(path2.c_str(), path3.c_str()), SyscallSucceeds()); - - // Read f's kContents. - EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(kContents), 0), - SyscallSucceedsWithValue(sizeof(kContents) - 1)); - EXPECT_EQ(absl::string_view(buf, sizeof(buf) - 1), kContents); -} - -// Tests that calling rename with file path ending with . or .. causes EBUSY. -TEST(RenameTest, PathEndingWithDots) { - TempPath root_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath dir1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); - TempPath dir2 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); - - // Try to move dir1 into dir2 but mess up the paths. - auto dir1Dot = JoinPath(dir1.path(), "."); - auto dir2Dot = JoinPath(dir2.path(), "."); - auto dir1DotDot = JoinPath(dir1.path(), ".."); - auto dir2DotDot = JoinPath(dir2.path(), ".."); - ASSERT_THAT(rename(dir1.path().c_str(), dir2Dot.c_str()), - SyscallFailsWithErrno(EBUSY)); - ASSERT_THAT(rename(dir1.path().c_str(), dir2DotDot.c_str()), - SyscallFailsWithErrno(EBUSY)); - ASSERT_THAT(rename(dir1Dot.c_str(), dir2.path().c_str()), - SyscallFailsWithErrno(EBUSY)); - ASSERT_THAT(rename(dir1DotDot.c_str(), dir2.path().c_str()), - SyscallFailsWithErrno(EBUSY)); -} - -// Calling rename with file path ending with . or .. causes EBUSY in sysfs. -TEST(RenameTest, SysfsPathEndingWithDots) { - // If a non-root user tries to rename inside /sys then we get EPERM. - SKIP_IF(geteuid() != 0); - ASSERT_THAT(rename("/sys/devices/system/cpu/online", "/sys/."), - SyscallFailsWithErrno(EBUSY)); - ASSERT_THAT(rename("/sys/devices/system/cpu/online", "/sys/.."), - SyscallFailsWithErrno(EBUSY)); -} - -TEST(RenameTest, SysfsFileToSelf) { - // If a non-root user tries to rename inside /sys then we get EPERM. - SKIP_IF(geteuid() != 0); - std::string const path = "/sys/devices/system/cpu/online"; - EXPECT_THAT(rename(path.c_str(), path.c_str()), SyscallSucceeds()); -} - -TEST(RenameTest, SysfsDirectoryToSelf) { - // If a non-root user tries to rename inside /sys then we get EPERM. - SKIP_IF(geteuid() != 0); - std::string const path = "/sys/devices"; - EXPECT_THAT(rename(path.c_str(), path.c_str()), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/rlimits.cc b/test/syscalls/linux/rlimits.cc deleted file mode 100644 index 860f0f688..000000000 --- a/test/syscalls/linux/rlimits.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/resource.h> -#include <sys/time.h> - -#include "test/util/capability_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(RlimitTest, SetRlimitHigher) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))); - - struct rlimit rl = {}; - EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); - - // Lower the rlimit first, as it may be equal to /proc/sys/fs/nr_open, in - // which case even users with CAP_SYS_RESOURCE can't raise it. - rl.rlim_cur--; - rl.rlim_max--; - ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); - - rl.rlim_max++; - EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); -} - -TEST(RlimitTest, UnprivilegedSetRlimit) { - // Drop privileges if necessary. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))) { - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_RESOURCE, false)); - } - - struct rlimit rl = {}; - rl.rlim_cur = 1000; - rl.rlim_max = 20000; - EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); - - struct rlimit rl2 = {}; - EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl2), SyscallSucceeds()); - EXPECT_EQ(rl.rlim_cur, rl2.rlim_cur); - EXPECT_EQ(rl.rlim_max, rl2.rlim_max); - - rl.rlim_max = 100000; - EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EPERM)); -} - -TEST(RlimitTest, SetSoftRlimitAboveHard) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))); - - struct rlimit rl = {}; - EXPECT_THAT(getrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); - - rl.rlim_cur = rl.rlim_max + 1; - EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/rseq.cc b/test/syscalls/linux/rseq.cc deleted file mode 100644 index 94f9154a0..000000000 --- a/test/syscalls/linux/rseq.cc +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <signal.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/rseq/test.h" -#include "test/syscalls/linux/rseq/uapi.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -// Syscall test for rseq (restartable sequences). -// -// We must be very careful about how these tests are written. Each thread may -// only have one struct rseq registration, which may be done automatically at -// thread start (as of 2019-11-13, glibc does *not* support rseq and thus does -// not do so, but other libraries do). -// -// Testing of rseq is thus done primarily in a child process with no -// registration. This means exec'ing a nostdlib binary, as rseq registration can -// only be cleared by execve (or knowing the old rseq address), and glibc (based -// on the current unmerged patches) register rseq before calling main()). - -int RSeq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t sig) { - return syscall(kRseqSyscall, rseq, rseq_len, flags, sig); -} - -// Returns true if this kernel supports the rseq syscall. -PosixErrorOr<bool> RSeqSupported() { - // We have to be careful here, there are three possible cases: - // - // 1. rseq is not supported -> ENOSYS - // 2. rseq is supported and not registered -> success, but we should - // unregister. - // 3. rseq is supported and registered -> EINVAL (most likely). - - // The only validation done on new registrations is that rseq is aligned and - // writable. - rseq rseq = {}; - int ret = RSeq(&rseq, sizeof(rseq), 0, 0); - if (ret == 0) { - // Successfully registered, rseq is supported. Unregister. - ret = RSeq(&rseq, sizeof(rseq), kRseqFlagUnregister, 0); - if (ret != 0) { - return PosixError(errno); - } - return true; - } - - switch (errno) { - case ENOSYS: - // Not supported. - return false; - case EINVAL: - // Supported, but already registered. EINVAL returned because we provided - // a different address. - return true; - default: - // Unknown error. - return PosixError(errno); - } -} - -constexpr char kRseqBinary[] = "test/syscalls/linux/rseq/rseq"; - -void RunChildTest(std::string test_case, int want_status) { - std::string path = RunfilePath(kRseqBinary); - - pid_t child_pid = -1; - int execve_errno = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(path, {path, test_case}, {}, &child_pid, &execve_errno)); - - ASSERT_GT(child_pid, 0); - ASSERT_EQ(execve_errno, 0); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - ASSERT_THAT(status, AnyOf(Eq(want_status), Eq(128 + want_status))); -} - -// Test that rseq must be aligned. -TEST(RseqTest, Unaligned) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestUnaligned, 0); -} - -// Sanity test that registration works. -TEST(RseqTest, Register) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestRegister, 0); -} - -// Registration can't be done twice. -TEST(RseqTest, DoubleRegister) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestDoubleRegister, 0); -} - -// Registration can be done again after unregister. -TEST(RseqTest, RegisterUnregister) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestRegisterUnregister, 0); -} - -// The pointer to rseq must match on register/unregister. -TEST(RseqTest, UnregisterDifferentPtr) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestUnregisterDifferentPtr, 0); -} - -// The signature must match on register/unregister. -TEST(RseqTest, UnregisterDifferentSignature) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestUnregisterDifferentSignature, 0); -} - -// The CPU ID is initialized. -TEST(RseqTest, CPU) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestCPU, 0); -} - -// Critical section is eventually aborted. -TEST(RseqTest, Abort) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestAbort, 0); -} - -// Abort may be before the critical section. -TEST(RseqTest, AbortBefore) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestAbortBefore, 0); -} - -// Signature must match. -TEST(RseqTest, AbortSignature) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestAbortSignature, SIGSEGV); -} - -// Abort must not be in the critical section. -TEST(RseqTest, AbortPreCommit) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestAbortPreCommit, SIGSEGV); -} - -// rseq.rseq_cs is cleared on abort. -TEST(RseqTest, AbortClearsCS) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestAbortClearsCS, 0); -} - -// rseq.rseq_cs is cleared on abort outside of critical section. -TEST(RseqTest, InvalidAbortClearsCS) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported())); - - RunChildTest(kRseqTestInvalidAbortClearsCS, 0); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/rseq/BUILD b/test/syscalls/linux/rseq/BUILD deleted file mode 100644 index 853258b04..000000000 --- a/test/syscalls/linux/rseq/BUILD +++ /dev/null @@ -1,61 +0,0 @@ -# This package contains a standalone rseq test binary. This binary must not -# depend on libc, which might use rseq itself. - -load("//tools:defs.bzl", "cc_flags_supplier", "cc_library", "cc_toolchain", "select_arch") - -package(licenses = ["notice"]) - -genrule( - name = "rseq_binary", - srcs = [ - "critical.h", - "critical_amd64.S", - "critical_arm64.S", - "rseq.cc", - "syscalls.h", - "start_amd64.S", - "start_arm64.S", - "test.h", - "types.h", - "uapi.h", - ], - outs = ["rseq"], - cmd = "$(CC) " + - "$(CC_FLAGS) " + - "-I. " + - "-Wall " + - "-Werror " + - "-O2 " + - "-std=c++17 " + - "-static " + - "-nostdlib " + - "-ffreestanding " + - "-o " + - "$(location rseq) " + - select_arch( - amd64 = "$(location critical_amd64.S) $(location start_amd64.S) ", - arm64 = "$(location critical_arm64.S) $(location start_arm64.S) ", - no_match_error = "unsupported architecture", - ) + - "$(location rseq.cc)", - toolchains = [ - cc_toolchain, - ":no_pie_cc_flags", - ], - visibility = ["//:sandbox"], -) - -cc_flags_supplier( - name = "no_pie_cc_flags", - features = ["-pie"], -) - -cc_library( - name = "lib", - testonly = 1, - hdrs = [ - "test.h", - "uapi.h", - ], - visibility = ["//:sandbox"], -) diff --git a/test/syscalls/linux/rseq/critical.h b/test/syscalls/linux/rseq/critical.h deleted file mode 100644 index ac987a25e..000000000 --- a/test/syscalls/linux/rseq/critical.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_ - -#include "test/syscalls/linux/rseq/types.h" -#include "test/syscalls/linux/rseq/uapi.h" - -constexpr uint32_t kRseqSignature = 0x90909090; - -extern "C" { - -extern void rseq_loop(struct rseq* r, struct rseq_cs* cs); -extern void* rseq_loop_early_abort; -extern void* rseq_loop_start; -extern void* rseq_loop_pre_commit; -extern void* rseq_loop_post_commit; -extern void* rseq_loop_abort; - -extern int rseq_getpid(struct rseq* r, struct rseq_cs* cs); -extern void* rseq_getpid_start; -extern void* rseq_getpid_post_commit; -extern void* rseq_getpid_abort; - -} // extern "C" - -#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_ diff --git a/test/syscalls/linux/rseq/critical_amd64.S b/test/syscalls/linux/rseq/critical_amd64.S deleted file mode 100644 index 8c0687e6d..000000000 --- a/test/syscalls/linux/rseq/critical_amd64.S +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Restartable sequences critical sections. - -// Loops continuously until aborted. -// -// void rseq_loop(struct rseq* r, struct rseq_cs* cs) - - .text - .globl rseq_loop - .type rseq_loop, @function - -rseq_loop: - jmp begin - - // Abort block before the critical section. - // Abort signature is 4 nops for simplicity. - .byte 0x90, 0x90, 0x90, 0x90 - .globl rseq_loop_early_abort -rseq_loop_early_abort: - ret - -begin: - // r->rseq_cs = cs - movq %rsi, 8(%rdi) - - // N.B. rseq_cs will be cleared by any preempt, even outside the critical - // section. Thus it must be set in or immediately before the critical section - // to ensure it is not cleared before the section begins. - .globl rseq_loop_start -rseq_loop_start: - jmp rseq_loop_start - - // "Pre-commit": extra instructions inside the critical section. These are - // used as the abort point in TestAbortPreCommit, which is not valid. - .globl rseq_loop_pre_commit -rseq_loop_pre_commit: - // Extra abort signature + nop for TestAbortPostCommit. - .byte 0x90, 0x90, 0x90, 0x90 - nop - - // "Post-commit": never reached in this case. - .globl rseq_loop_post_commit -rseq_loop_post_commit: - - // Abort signature is 4 nops for simplicity. - .byte 0x90, 0x90, 0x90, 0x90 - - .globl rseq_loop_abort -rseq_loop_abort: - ret - - .size rseq_loop,.-rseq_loop - .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/critical_arm64.S b/test/syscalls/linux/rseq/critical_arm64.S deleted file mode 100644 index bfe7e8307..000000000 --- a/test/syscalls/linux/rseq/critical_arm64.S +++ /dev/null @@ -1,66 +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. - -// Restartable sequences critical sections. - -// Loops continuously until aborted. -// -// void rseq_loop(struct rseq* r, struct rseq_cs* cs) - - .text - .globl rseq_loop - .type rseq_loop, @function - -rseq_loop: - b begin - - // Abort block before the critical section. - // Abort signature. - .byte 0x90, 0x90, 0x90, 0x90 - .globl rseq_loop_early_abort -rseq_loop_early_abort: - ret - -begin: - // r->rseq_cs = cs - str x1, [x0, #8] - - // N.B. rseq_cs will be cleared by any preempt, even outside the critical - // section. Thus it must be set in or immediately before the critical section - // to ensure it is not cleared before the section begins. - .globl rseq_loop_start -rseq_loop_start: - b rseq_loop_start - - // "Pre-commit": extra instructions inside the critical section. These are - // used as the abort point in TestAbortPreCommit, which is not valid. - .globl rseq_loop_pre_commit -rseq_loop_pre_commit: - // Extra abort signature + nop for TestAbortPostCommit. - .byte 0x90, 0x90, 0x90, 0x90 - nop - - // "Post-commit": never reached in this case. - .globl rseq_loop_post_commit -rseq_loop_post_commit: - - // Abort signature. - .byte 0x90, 0x90, 0x90, 0x90 - - .globl rseq_loop_abort -rseq_loop_abort: - ret - - .size rseq_loop,.-rseq_loop - .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/rseq.cc b/test/syscalls/linux/rseq/rseq.cc deleted file mode 100644 index 6f5d38bba..000000000 --- a/test/syscalls/linux/rseq/rseq.cc +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/rseq/critical.h" -#include "test/syscalls/linux/rseq/syscalls.h" -#include "test/syscalls/linux/rseq/test.h" -#include "test/syscalls/linux/rseq/types.h" -#include "test/syscalls/linux/rseq/uapi.h" - -namespace gvisor { -namespace testing { - -extern "C" int main(int argc, char** argv, char** envp); - -// Standalone initialization before calling main(). -extern "C" void __init(uintptr_t* sp) { - int argc = sp[0]; - char** argv = reinterpret_cast<char**>(&sp[1]); - char** envp = &argv[argc + 1]; - - // Call main() and exit. - sys_exit_group(main(argc, argv, envp)); - - // sys_exit_group does not return -} - -int strcmp(const char* s1, const char* s2) { - const unsigned char* p1 = reinterpret_cast<const unsigned char*>(s1); - const unsigned char* p2 = reinterpret_cast<const unsigned char*>(s2); - - while (*p1 == *p2) { - if (!*p1) { - return 0; - } - ++p1; - ++p2; - } - return static_cast<int>(*p1) - static_cast<int>(*p2); -} - -int sys_rseq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t sig) { - return raw_syscall(kRseqSyscall, rseq, rseq_len, flags, sig); -} - -// Test that rseq must be aligned. -int TestUnaligned() { - constexpr uintptr_t kRequiredAlignment = alignof(rseq); - - char buf[2 * kRequiredAlignment] = {}; - uintptr_t ptr = reinterpret_cast<uintptr_t>(&buf[0]); - if ((ptr & (kRequiredAlignment - 1)) == 0) { - // buf is already aligned. Misalign it. - ptr++; - } - - int ret = sys_rseq(reinterpret_cast<rseq*>(ptr), sizeof(rseq), 0, 0); - if (sys_errno(ret) != EINVAL) { - return 1; - } - return 0; -} - -// Sanity test that registration works. -int TestRegister() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - return 0; -} - -// Registration can't be done twice. -int TestDoubleRegister() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != EBUSY) { - return 1; - } - - return 0; -} - -// Registration can be done again after unregister. -int TestRegisterUnregister() { - struct rseq r = {}; - - int ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - return 0; -} - -// The pointer to rseq must match on register/unregister. -int TestUnregisterDifferentPtr() { - struct rseq r = {}; - - int ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq r2 = {}; - - ret = sys_rseq(&r2, sizeof(r2), kRseqFlagUnregister, 0); - if (sys_errno(ret) != EINVAL) { - return 1; - } - - return 0; -} - -// The signature must match on register/unregister. -int TestUnregisterDifferentSignature() { - constexpr int kSignature = 0; - - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kSignature); - if (sys_errno(ret) != 0) { - return 1; - } - - ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, kSignature + 1); - if (sys_errno(ret) != EPERM) { - return 1; - } - - return 0; -} - -// The CPU ID is initialized. -int TestCPU() { - struct rseq r = {}; - r.cpu_id = kRseqCPUIDUninitialized; - - int ret = sys_rseq(&r, sizeof(r), 0, 0); - if (sys_errno(ret) != 0) { - return 1; - } - - if (__atomic_load_n(&r.cpu_id, __ATOMIC_RELAXED) < 0) { - return 1; - } - if (__atomic_load_n(&r.cpu_id_start, __ATOMIC_RELAXED) < 0) { - return 1; - } - - return 0; -} - -// Critical section is eventually aborted. -int TestAbort() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort); - - // Loops until abort. If this returns then abort occurred. - rseq_loop(&r, &cs); - - return 0; -} - -// Abort may be before the critical section. -int TestAbortBefore() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_early_abort); - - // Loops until abort. If this returns then abort occurred. - rseq_loop(&r, &cs); - - return 0; -} - -// Signature must match. -int TestAbortSignature() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort); - - // Loops until abort. This should SIGSEGV on abort. - rseq_loop(&r, &cs); - - return 1; -} - -// Abort must not be in the critical section. -int TestAbortPreCommit() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_pre_commit); - - // Loops until abort. This should SIGSEGV on abort. - rseq_loop(&r, &cs); - - return 1; -} - -// rseq.rseq_cs is cleared on abort. -int TestAbortClearsCS() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort); - - // Loops until abort. If this returns then abort occurred. - rseq_loop(&r, &cs); - - if (__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) { - return 1; - } - - return 0; -} - -// rseq.rseq_cs is cleared on abort outside of critical section. -int TestInvalidAbortClearsCS() { - struct rseq r = {}; - int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature); - if (sys_errno(ret) != 0) { - return 1; - } - - struct rseq_cs cs = {}; - cs.version = 0; - cs.flags = 0; - cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) - - reinterpret_cast<uint64_t>(&rseq_loop_start); - cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort); - - __atomic_store_n(&r.rseq_cs, &cs, __ATOMIC_RELAXED); - - // When the next abort condition occurs, the kernel will clear cs once it - // determines we aren't in the critical section. - while (1) { - if (!__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) { - break; - } - } - - return 0; -} - -// Exit codes: -// 0 - Pass -// 1 - Fail -// 2 - Missing argument -// 3 - Unknown test case -extern "C" int main(int argc, char** argv, char** envp) { - if (argc != 2) { - // Usage: rseq <test case> - return 2; - } - - if (strcmp(argv[1], kRseqTestUnaligned) == 0) { - return TestUnaligned(); - } - if (strcmp(argv[1], kRseqTestRegister) == 0) { - return TestRegister(); - } - if (strcmp(argv[1], kRseqTestDoubleRegister) == 0) { - return TestDoubleRegister(); - } - if (strcmp(argv[1], kRseqTestRegisterUnregister) == 0) { - return TestRegisterUnregister(); - } - if (strcmp(argv[1], kRseqTestUnregisterDifferentPtr) == 0) { - return TestUnregisterDifferentPtr(); - } - if (strcmp(argv[1], kRseqTestUnregisterDifferentSignature) == 0) { - return TestUnregisterDifferentSignature(); - } - if (strcmp(argv[1], kRseqTestCPU) == 0) { - return TestCPU(); - } - if (strcmp(argv[1], kRseqTestAbort) == 0) { - return TestAbort(); - } - if (strcmp(argv[1], kRseqTestAbortBefore) == 0) { - return TestAbortBefore(); - } - if (strcmp(argv[1], kRseqTestAbortSignature) == 0) { - return TestAbortSignature(); - } - if (strcmp(argv[1], kRseqTestAbortPreCommit) == 0) { - return TestAbortPreCommit(); - } - if (strcmp(argv[1], kRseqTestAbortClearsCS) == 0) { - return TestAbortClearsCS(); - } - if (strcmp(argv[1], kRseqTestInvalidAbortClearsCS) == 0) { - return TestInvalidAbortClearsCS(); - } - - return 3; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/rseq/start_amd64.S b/test/syscalls/linux/rseq/start_amd64.S deleted file mode 100644 index b9611b276..000000000 --- a/test/syscalls/linux/rseq/start_amd64.S +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - - .text - .align 4 - .type _start,@function - .globl _start - -_start: - movq %rsp,%rdi - call __init - hlt - - .size _start,.-_start - .section .note.GNU-stack,"",@progbits - - .text - .globl raw_syscall - .type raw_syscall, @function - -raw_syscall: - mov %rdi,%rax // syscall # - mov %rsi,%rdi // arg0 - mov %rdx,%rsi // arg1 - mov %rcx,%rdx // arg2 - mov %r8,%r10 // arg3 (goes in r10 instead of rcx for system calls) - mov %r9,%r8 // arg4 - mov 0x8(%rsp),%r9 // arg5 - syscall - ret - - .size raw_syscall,.-raw_syscall - .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/start_arm64.S b/test/syscalls/linux/rseq/start_arm64.S deleted file mode 100644 index 693c1c6eb..000000000 --- a/test/syscalls/linux/rseq/start_arm64.S +++ /dev/null @@ -1,45 +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. - - - .text - .align 4 - .type _start,@function - .globl _start - -_start: - mov x29, sp - bl __init - wfi - - .size _start,.-_start - .section .note.GNU-stack,"",@progbits - - .text - .globl raw_syscall - .type raw_syscall, @function - -raw_syscall: - mov x8,x0 // syscall # - mov x0,x1 // arg0 - mov x1,x2 // arg1 - mov x2,x3 // arg2 - mov x3,x4 // arg3 - mov x4,x5 // arg4 - mov x5,x6 // arg5 - svc #0 - ret - - .size raw_syscall,.-raw_syscall - .section .note.GNU-stack,"",@progbits diff --git a/test/syscalls/linux/rseq/syscalls.h b/test/syscalls/linux/rseq/syscalls.h deleted file mode 100644 index c4118e6c5..000000000 --- a/test/syscalls/linux/rseq/syscalls.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_ - -#include "test/syscalls/linux/rseq/types.h" - -// Syscall numbers. -#if defined(__x86_64__) -constexpr int kGetpid = 39; -constexpr int kExitGroup = 231; -#elif defined(__aarch64__) -constexpr int kGetpid = 172; -constexpr int kExitGroup = 94; -#else -#error "Unknown architecture" -#endif - -namespace gvisor { -namespace testing { - -// Standalone system call interfaces. -// Note that these are all "raw" system call interfaces which encode -// errors by setting the return value to a small negative number. -// Use sys_errno() to check system call return values for errors. - -// Maximum Linux error number. -constexpr int kMaxErrno = 4095; - -// Errno values. -#define EPERM 1 -#define EFAULT 14 -#define EBUSY 16 -#define EINVAL 22 - -// Get the error number from a raw system call return value. -// Returns a positive error number or 0 if there was no error. -static inline int sys_errno(uintptr_t rval) { - if (rval >= static_cast<uintptr_t>(-kMaxErrno)) { - return -static_cast<int>(rval); - } - return 0; -} - -extern "C" uintptr_t raw_syscall(int number, ...); - -static inline void sys_exit_group(int status) { - raw_syscall(kExitGroup, status); -} -static inline int sys_getpid() { - return static_cast<int>(raw_syscall(kGetpid)); -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_ diff --git a/test/syscalls/linux/rseq/test.h b/test/syscalls/linux/rseq/test.h deleted file mode 100644 index ff0dd6e48..000000000 --- a/test/syscalls/linux/rseq/test.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_ - -namespace gvisor { -namespace testing { - -// Test cases supported by rseq binary. - -constexpr char kRseqTestUnaligned[] = "unaligned"; -constexpr char kRseqTestRegister[] = "register"; -constexpr char kRseqTestDoubleRegister[] = "double-register"; -constexpr char kRseqTestRegisterUnregister[] = "register-unregister"; -constexpr char kRseqTestUnregisterDifferentPtr[] = "unregister-different-ptr"; -constexpr char kRseqTestUnregisterDifferentSignature[] = - "unregister-different-signature"; -constexpr char kRseqTestCPU[] = "cpu"; -constexpr char kRseqTestAbort[] = "abort"; -constexpr char kRseqTestAbortBefore[] = "abort-before"; -constexpr char kRseqTestAbortSignature[] = "abort-signature"; -constexpr char kRseqTestAbortPreCommit[] = "abort-precommit"; -constexpr char kRseqTestAbortClearsCS[] = "abort-clears-cs"; -constexpr char kRseqTestInvalidAbortClearsCS[] = "invalid-abort-clears-cs"; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_ diff --git a/test/syscalls/linux/rseq/types.h b/test/syscalls/linux/rseq/types.h deleted file mode 100644 index b6afe9817..000000000 --- a/test/syscalls/linux/rseq/types.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_ - -using size_t = __SIZE_TYPE__; -using uintptr_t = __UINTPTR_TYPE__; - -using uint8_t = __UINT8_TYPE__; -using uint16_t = __UINT16_TYPE__; -using uint32_t = __UINT32_TYPE__; -using uint64_t = __UINT64_TYPE__; - -using int8_t = __INT8_TYPE__; -using int16_t = __INT16_TYPE__; -using int32_t = __INT32_TYPE__; -using int64_t = __INT64_TYPE__; - -#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_ diff --git a/test/syscalls/linux/rseq/uapi.h b/test/syscalls/linux/rseq/uapi.h deleted file mode 100644 index d3e60d0a4..000000000 --- a/test/syscalls/linux/rseq/uapi.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_ - -#include <stdint.h> - -// User-kernel ABI for restartable sequences. - -// Syscall numbers. -#if defined(__x86_64__) -constexpr int kRseqSyscall = 334; -#elif defined(__aarch64__) -constexpr int kRseqSyscall = 293; -#else -#error "Unknown architecture" -#endif // __x86_64__ - -struct rseq_cs { - uint32_t version; - uint32_t flags; - uint64_t start_ip; - uint64_t post_commit_offset; - uint64_t abort_ip; -} __attribute__((aligned(4 * sizeof(uint64_t)))); - -// N.B. alignment is enforced by the kernel. -struct rseq { - uint32_t cpu_id_start; - uint32_t cpu_id; - struct rseq_cs* rseq_cs; - uint32_t flags; -} __attribute__((aligned(4 * sizeof(uint64_t)))); - -constexpr int kRseqFlagUnregister = 1 << 0; - -constexpr int kRseqCPUIDUninitialized = -1; - -#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_ diff --git a/test/syscalls/linux/rtsignal.cc b/test/syscalls/linux/rtsignal.cc deleted file mode 100644 index ed27e2566..000000000 --- a/test/syscalls/linux/rtsignal.cc +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <cerrno> -#include <csignal> - -#include "gtest/gtest.h" -#include "test/util/cleanup.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// saved_info is set by the handler. -siginfo_t saved_info; - -// has_saved_info is set to true by the handler. -volatile bool has_saved_info; - -void SigHandler(int sig, siginfo_t* info, void* context) { - // Copy to the given info. - saved_info = *info; - has_saved_info = true; -} - -void ClearSavedInfo() { - // Clear the cached info. - memset(&saved_info, 0, sizeof(saved_info)); - has_saved_info = false; -} - -PosixErrorOr<Cleanup> SetupSignalHandler(int sig) { - struct sigaction sa; - sa.sa_sigaction = SigHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - return ScopedSigaction(sig, sa); -} - -class RtSignalTest : public ::testing::Test { - protected: - void SetUp() override { - action_cleanup_ = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR1)); - mask_cleanup_ = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR1)); - } - - void TearDown() override { ClearSavedInfo(); } - - private: - Cleanup action_cleanup_; - Cleanup mask_cleanup_; -}; - -static int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t* uinfo) { - int ret; - do { - // NOTE(b/25434735): rt_sigqueueinfo(2) could return EAGAIN for RT signals. - ret = syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo); - } while (ret == -1 && errno == EAGAIN); - return ret; -} - -TEST_F(RtSignalTest, InvalidTID) { - siginfo_t uinfo; - // Depending on the kernel version, these calls may fail with - // ESRCH (goobunutu machines) or EPERM (production machines). Thus, - // the test simply ensures that they do fail. - EXPECT_THAT(rt_sigqueueinfo(-1, SIGUSR1, &uinfo), SyscallFails()); - EXPECT_FALSE(has_saved_info); - EXPECT_THAT(rt_sigqueueinfo(0, SIGUSR1, &uinfo), SyscallFails()); - EXPECT_FALSE(has_saved_info); -} - -TEST_F(RtSignalTest, InvalidCodes) { - siginfo_t uinfo; - - // We need a child for the code checks to apply. If the process is delivering - // to itself, then it can use whatever codes it wants and they will go - // through. - pid_t child = fork(); - if (child == 0) { - _exit(1); - } - ASSERT_THAT(child, SyscallSucceeds()); - - // These are not allowed for child processes. - uinfo.si_code = 0; // SI_USER. - EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), - SyscallFailsWithErrno(EPERM)); - uinfo.si_code = 0x80; // SI_KERNEL. - EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), - SyscallFailsWithErrno(EPERM)); - uinfo.si_code = -6; // SI_TKILL. - EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), - SyscallFailsWithErrno(EPERM)); - uinfo.si_code = -1; // SI_QUEUE (allowed). - EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), SyscallSucceeds()); - - // Join the child process. - EXPECT_THAT(waitpid(child, nullptr, 0), SyscallSucceeds()); -} - -TEST_F(RtSignalTest, ValueDelivered) { - siginfo_t uinfo; - uinfo.si_code = -1; // SI_QUEUE (allowed). - uinfo.si_errno = 0x1234; - - EXPECT_EQ(saved_info.si_errno, 0x0); - EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds()); - EXPECT_TRUE(has_saved_info); - EXPECT_EQ(saved_info.si_errno, 0x1234); -} - -TEST_F(RtSignalTest, SignoMatch) { - auto action2_cleanup = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR2)); - auto mask2_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR2)); - - siginfo_t uinfo; - uinfo.si_code = -1; // SI_QUEUE (allowed). - - EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds()); - EXPECT_TRUE(has_saved_info); - EXPECT_EQ(saved_info.si_signo, SIGUSR1); - - ClearSavedInfo(); - - EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR2, &uinfo), SyscallSucceeds()); - EXPECT_TRUE(has_saved_info); - EXPECT_EQ(saved_info.si_signo, SIGUSR2); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // These tests depend on delivering SIGUSR1/2 to the main thread (so they can - // synchronously check has_saved_info). Block these so that any other threads - // created by TestInit will also have them blocked. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGUSR1); - sigaddset(&set, SIGUSR2); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/sched.cc b/test/syscalls/linux/sched.cc deleted file mode 100644 index 735e99411..000000000 --- a/test/syscalls/linux/sched.cc +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sched.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// In linux, pid is limited to 29 bits because how futex is implemented. -constexpr int kImpossiblePID = (1 << 29) + 1; - -TEST(SchedGetparamTest, ReturnsZero) { - struct sched_param param; - EXPECT_THAT(sched_getparam(getpid(), ¶m), SyscallSucceeds()); - EXPECT_EQ(param.sched_priority, 0); - EXPECT_THAT(sched_getparam(/*pid=*/0, ¶m), SyscallSucceeds()); - EXPECT_EQ(param.sched_priority, 0); -} - -TEST(SchedGetparamTest, InvalidPIDReturnsEINVAL) { - struct sched_param param; - EXPECT_THAT(sched_getparam(/*pid=*/-1, ¶m), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SchedGetparamTest, ImpossiblePIDReturnsESRCH) { - struct sched_param param; - EXPECT_THAT(sched_getparam(kImpossiblePID, ¶m), - SyscallFailsWithErrno(ESRCH)); -} - -TEST(SchedGetparamTest, NullParamReturnsEINVAL) { - EXPECT_THAT(sched_getparam(0, nullptr), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SchedGetschedulerTest, ReturnsSchedOther) { - EXPECT_THAT(sched_getscheduler(getpid()), - SyscallSucceedsWithValue(SCHED_OTHER)); - EXPECT_THAT(sched_getscheduler(/*pid=*/0), - SyscallSucceedsWithValue(SCHED_OTHER)); -} - -TEST(SchedGetschedulerTest, ReturnsEINVAL) { - EXPECT_THAT(sched_getscheduler(/*pid=*/-1), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SchedGetschedulerTest, ReturnsESRCH) { - EXPECT_THAT(sched_getscheduler(kImpossiblePID), SyscallFailsWithErrno(ESRCH)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sched_yield.cc b/test/syscalls/linux/sched_yield.cc deleted file mode 100644 index 5d24f5b58..000000000 --- a/test/syscalls/linux/sched_yield.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sched.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SchedYieldTest, Success) { - EXPECT_THAT(sched_yield(), SyscallSucceeds()); - EXPECT_THAT(sched_yield(), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/seccomp.cc b/test/syscalls/linux/seccomp.cc deleted file mode 100644 index ce88d90dd..000000000 --- a/test/syscalls/linux/seccomp.cc +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <linux/audit.h> -#include <linux/filter.h> -#include <linux/seccomp.h> -#include <pthread.h> -#include <sched.h> -#include <signal.h> -#include <string.h> -#include <sys/prctl.h> -#include <sys/syscall.h> -#include <time.h> -#include <ucontext.h> -#include <unistd.h> - -#include <atomic> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/logging.h" -#include "test/util/memory_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -#ifndef SYS_SECCOMP -#define SYS_SECCOMP 1 -#endif - -namespace gvisor { -namespace testing { - -namespace { - -// A syscall not implemented by Linux that we don't expect to be called. -#ifdef __x86_64__ -constexpr uint32_t kFilteredSyscall = SYS_vserver; -#elif __aarch64__ -// Use the last of arch_specific_syscalls which are not implemented on arm64. -constexpr uint32_t kFilteredSyscall = __NR_arch_specific_syscall + 15; -#endif - -// Applies a seccomp-bpf filter that returns `filtered_result` for -// `sysno` and allows all other syscalls. Async-signal-safe. -void ApplySeccompFilter(uint32_t sysno, uint32_t filtered_result, - uint32_t flags = 0) { - // "Prior to [PR_SET_SECCOMP], the task must call prctl(PR_SET_NO_NEW_PRIVS, - // 1) or run with CAP_SYS_ADMIN privileges in its namespace." - - // Documentation/prctl/seccomp_filter.txt - // - // prctl(PR_SET_NO_NEW_PRIVS, 1) may be called repeatedly; calls after the - // first are no-ops. - TEST_PCHECK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0); - MaybeSave(); - - struct sock_filter filter[] = { - // A = seccomp_data.arch - BPF_STMT(BPF_LD | BPF_ABS | BPF_W, 4), -#if defined(__x86_64__) - // if (A != AUDIT_ARCH_X86_64) goto kill - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 4), -#elif defined(__aarch64__) - // if (A != AUDIT_ARCH_AARCH64) goto kill - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_AARCH64, 0, 4), -#else -#error "Unknown architecture" -#endif - // A = seccomp_data.nr - BPF_STMT(BPF_LD | BPF_ABS | BPF_W, 0), - // if (A != sysno) goto allow - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, sysno, 0, 1), - // return filtered_result - BPF_STMT(BPF_RET | BPF_K, filtered_result), - // allow: return SECCOMP_RET_ALLOW - BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - // kill: return SECCOMP_RET_KILL - BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), - }; - struct sock_fprog prog; - prog.len = ABSL_ARRAYSIZE(filter); - prog.filter = filter; - if (flags) { - TEST_CHECK(syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, flags, &prog) == - 0); - } else { - TEST_PCHECK(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) == 0); - } - MaybeSave(); -} - -// Wrapper for sigaction. Async-signal-safe. -void RegisterSignalHandler(int signum, - void (*handler)(int, siginfo_t*, void*)) { - struct sigaction sa = {}; - sa.sa_sigaction = handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - TEST_PCHECK(sigaction(signum, &sa, nullptr) == 0); - MaybeSave(); -} - -// All of the following tests execute in a subprocess to ensure that each test -// is run in a separate process. This avoids cross-contamination of seccomp -// state between tests, and is necessary to ensure that test processes killed -// by SECCOMP_RET_KILL are single-threaded (since SECCOMP_RET_KILL only kills -// the offending thread, not the whole thread group). - -TEST(SeccompTest, RetKillCausesDeathBySIGSYS) { - pid_t const pid = fork(); - if (pid == 0) { - // Register a signal handler for SIGSYS that we don't expect to be invoked. - RegisterSignalHandler( - SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); }); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL); - syscall(kFilteredSyscall); - TEST_CHECK_MSG(false, "Survived invocation of test syscall"); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS) - << "status " << status; -} - -TEST(SeccompTest, RetKillOnlyKillsOneThread) { - Mapping stack = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - - pid_t const pid = fork(); - if (pid == 0) { - // Register a signal handler for SIGSYS that we don't expect to be invoked. - RegisterSignalHandler( - SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); }); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL); - // Pass CLONE_VFORK to block the original thread in the child process until - // the clone thread exits with SIGSYS. - // - // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's - // x86_64 implementation is safe. See glibc - // sysdeps/unix/sysv/linux/x86_64/clone.S. - clone( - +[](void* arg) { - syscall(kFilteredSyscall); // should kill the thread - _exit(1); // should be unreachable - return 2; // should be very unreachable, shut up the compiler - }, - stack.endptr(), - CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM | - CLONE_VFORK, - nullptr); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -TEST(SeccompTest, RetTrapCausesSIGSYS) { - pid_t const pid = fork(); - if (pid == 0) { - constexpr uint16_t kTrapValue = 0xdead; - RegisterSignalHandler( - SIGSYS, +[](int signo, siginfo_t* info, void* ucv) { - ucontext_t* uc = static_cast<ucontext_t*>(ucv); - // This is a signal handler, so we must stay async-signal-safe. - TEST_CHECK(info->si_signo == SIGSYS); - TEST_CHECK(info->si_code == SYS_SECCOMP); - TEST_CHECK(info->si_errno == kTrapValue); - TEST_CHECK(info->si_call_addr != nullptr); - TEST_CHECK(info->si_syscall == kFilteredSyscall); -#if defined(__x86_64__) - TEST_CHECK(info->si_arch == AUDIT_ARCH_X86_64); - TEST_CHECK(uc->uc_mcontext.gregs[REG_RAX] == kFilteredSyscall); -#elif defined(__aarch64__) - TEST_CHECK(info->si_arch == AUDIT_ARCH_AARCH64); - TEST_CHECK(uc->uc_mcontext.regs[8] == kFilteredSyscall); -#endif // defined(__x86_64__) - _exit(0); - }); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRAP | kTrapValue); - syscall(kFilteredSyscall); - TEST_CHECK_MSG(false, "Survived invocation of test syscall"); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -#ifdef __x86_64__ - -constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400; - -time_t vsyscall_time(time_t* t) { - return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t); -} - -TEST(SeccompTest, SeccompAppliesToVsyscall) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - - pid_t const pid = fork(); - if (pid == 0) { - constexpr uint16_t kTrapValue = 0xdead; - RegisterSignalHandler( - SIGSYS, +[](int signo, siginfo_t* info, void* ucv) { - ucontext_t* uc = static_cast<ucontext_t*>(ucv); - // This is a signal handler, so we must stay async-signal-safe. - TEST_CHECK(info->si_signo == SIGSYS); - TEST_CHECK(info->si_code == SYS_SECCOMP); - TEST_CHECK(info->si_errno == kTrapValue); - TEST_CHECK(info->si_call_addr != nullptr); - TEST_CHECK(info->si_syscall == SYS_time); - TEST_CHECK(info->si_arch == AUDIT_ARCH_X86_64); - TEST_CHECK(uc->uc_mcontext.gregs[REG_RAX] == SYS_time); - _exit(0); - }); - ApplySeccompFilter(SYS_time, SECCOMP_RET_TRAP | kTrapValue); - vsyscall_time(nullptr); // Should result in death. - TEST_CHECK_MSG(false, "Survived invocation of test syscall"); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -TEST(SeccompTest, RetKillVsyscallCausesDeathBySIGSYS) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - - pid_t const pid = fork(); - if (pid == 0) { - // Register a signal handler for SIGSYS that we don't expect to be invoked. - RegisterSignalHandler( - SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); }); - ApplySeccompFilter(SYS_time, SECCOMP_RET_KILL); - vsyscall_time(nullptr); // Should result in death. - TEST_CHECK_MSG(false, "Survived invocation of test syscall"); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS) - << "status " << status; -} - -#endif // defined(__x86_64__) - -TEST(SeccompTest, RetTraceWithoutPtracerReturnsENOSYS) { - pid_t const pid = fork(); - if (pid == 0) { - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRACE); - TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -TEST(SeccompTest, RetErrnoReturnsErrno) { - pid_t const pid = fork(); - if (pid == 0) { - // ENOTNAM: "Not a XENIX named type file" - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM); - TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOTNAM); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -TEST(SeccompTest, RetAllowAllowsSyscall) { - pid_t const pid = fork(); - if (pid == 0) { - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ALLOW); - TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -// This test will validate that TSYNC will apply to all threads. -TEST(SeccompTest, TsyncAppliesToAllThreads) { - Mapping stack = ASSERT_NO_ERRNO_AND_VALUE( - MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - - // We don't want to apply this policy to other test runner threads, so fork. - const pid_t pid = fork(); - - if (pid == 0) { - // First check that we receive a ENOSYS before the policy is applied. - TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOSYS); - - // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's - // x86_64 implementation is safe. See glibc - // sysdeps/unix/sysv/linux/x86_64/clone.S. - clone( - +[](void* arg) { - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM, - SECCOMP_FILTER_FLAG_TSYNC); - return 0; - }, - stack.endptr(), - CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM | - CLONE_VFORK, - nullptr); - - // Because we're using CLONE_VFORK this thread will be blocked until - // the second thread has released resources to our virtual memory, since - // we're not execing that will happen on _exit. - - // Now verify that the policy applied to this thread too. - TEST_CHECK(syscall(kFilteredSyscall) == -1 && errno == ENOTNAM); - _exit(0); - } - - ASSERT_THAT(pid, SyscallSucceeds()); - int status = 0; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -// This test will validate that seccomp(2) rejects unsupported flags. -TEST(SeccompTest, SeccompRejectsUnknownFlags) { - constexpr uint32_t kInvalidFlag = 123; - ASSERT_THAT( - syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, kInvalidFlag, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SeccompTest, LeastPermissiveFilterReturnValueApplies) { - // This is RetKillCausesDeathBySIGSYS, plus extra filters before and after the - // one that causes the kill that should be ignored. - pid_t const pid = fork(); - if (pid == 0) { - RegisterSignalHandler( - SIGSYS, +[](int, siginfo_t*, void*) { _exit(1); }); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_TRACE); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL); - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_ERRNO | ENOTNAM); - syscall(kFilteredSyscall); - TEST_CHECK_MSG(false, "Survived invocation of test syscall"); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS) - << "status " << status; -} - -// Passed as argv[1] to cause the test binary to invoke kFilteredSyscall and -// exit. Not a real flag since flag parsing happens during initialization, -// which may create threads. -constexpr char kInvokeFilteredSyscallFlag[] = "--seccomp_test_child"; - -TEST(SeccompTest, FiltersPreservedAcrossForkAndExecve) { - ExecveArray const grandchild_argv( - {"/proc/self/exe", kInvokeFilteredSyscallFlag}); - - pid_t const pid = fork(); - if (pid == 0) { - ApplySeccompFilter(kFilteredSyscall, SECCOMP_RET_KILL); - pid_t const grandchild_pid = fork(); - if (grandchild_pid == 0) { - execve(grandchild_argv.get()[0], grandchild_argv.get(), - /* envp = */ nullptr); - TEST_PCHECK_MSG(false, "execve failed"); - } - int status; - TEST_PCHECK(waitpid(grandchild_pid, &status, 0) == grandchild_pid); - TEST_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status " << status; -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - if (argc >= 2 && - strcmp(argv[1], gvisor::testing::kInvokeFilteredSyscallFlag) == 0) { - syscall(gvisor::testing::kFilteredSyscall); - exit(0); - } - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/select.cc b/test/syscalls/linux/select.cc deleted file mode 100644 index be2364fb8..000000000 --- a/test/syscalls/linux/select.cc +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/resource.h> -#include <sys/select.h> -#include <sys/time.h> - -#include <climits> -#include <csignal> -#include <cstdio> - -#include "gtest/gtest.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/base_poll_test.h" -#include "test/util/file_descriptor.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/rlimit_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -class SelectTest : public BasePollTest { - protected: - void SetUp() override { BasePollTest::SetUp(); } - void TearDown() override { BasePollTest::TearDown(); } -}; - -// See that when there are no FD sets, select behaves like sleep. -TEST_F(SelectTest, NullFds) { - struct timeval timeout = absl::ToTimeval(absl::Milliseconds(10)); - ASSERT_THAT(select(0, nullptr, nullptr, nullptr, &timeout), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_usec, 0); - - timeout = absl::ToTimeval(absl::Milliseconds(10)); - ASSERT_THAT(select(1, nullptr, nullptr, nullptr, &timeout), - SyscallSucceeds()); - EXPECT_EQ(timeout.tv_sec, 0); - EXPECT_EQ(timeout.tv_usec, 0); -} - -TEST_F(SelectTest, NegativeNfds) { - EXPECT_THAT(select(-1, nullptr, nullptr, nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(select(-100000, nullptr, nullptr, nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(select(INT_MIN, nullptr, nullptr, nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(SelectTest, ClosedFds) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); - - // We can't rely on a file descriptor being closed in a multi threaded - // application so fork to get a clean process. - EXPECT_THAT(InForkedProcess([&] { - int fd_num = fd.get(); - fd.reset(); - - fd_set read_set; - FD_ZERO(&read_set); - FD_SET(fd_num, &read_set); - - struct timeval timeout = - absl::ToTimeval(absl::Milliseconds(10)); - TEST_PCHECK(select(fd_num + 1, &read_set, nullptr, nullptr, - &timeout) != 0); - TEST_PCHECK(errno == EBADF); - }), - IsPosixErrorOkAndHolds(0)); -} - -TEST_F(SelectTest, ZeroTimeout) { - struct timeval timeout = {}; - EXPECT_THAT(select(1, nullptr, nullptr, nullptr, &timeout), - SyscallSucceeds()); - // Ignore timeout as its value is now undefined. -} - -// If random S/R interrupts the select, SIGALRM may be delivered before select -// restarts, causing the select to hang forever. -TEST_F(SelectTest, NoTimeout_NoRandomSave) { - // When there's no timeout, select may never return so set a timer. - SetTimer(absl::Milliseconds(100)); - // See that we get interrupted by the timer. - ASSERT_THAT(select(1, nullptr, nullptr, nullptr, nullptr), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); -} - -TEST_F(SelectTest, InvalidTimeoutNegative) { - struct timeval timeout = absl::ToTimeval(absl::Microseconds(-1)); - EXPECT_THAT(select(1, nullptr, nullptr, nullptr, &timeout), - SyscallFailsWithErrno(EINVAL)); - // Ignore timeout as its value is now undefined. -} - -// Verify that a signal interrupts select. -// -// If random S/R interrupts the select, SIGALRM may be delivered before select -// restarts, causing the select to hang forever. -TEST_F(SelectTest, InterruptedBySignal_NoRandomSave) { - absl::Duration duration(absl::Seconds(5)); - struct timeval timeout = absl::ToTimeval(duration); - SetTimer(absl::Milliseconds(100)); - ASSERT_FALSE(TimerFired()); - ASSERT_THAT(select(1, nullptr, nullptr, nullptr, &timeout), - SyscallFailsWithErrno(EINTR)); - EXPECT_TRUE(TimerFired()); - // Ignore timeout as its value is now undefined. -} - -TEST_F(SelectTest, IgnoreBitsAboveNfds) { - // fd_set is a bit array with at least FD_SETSIZE bits. Test that bits - // corresponding to file descriptors above nfds are ignored. - fd_set read_set; - FD_ZERO(&read_set); - constexpr int kNfds = 1; - for (int fd = kNfds; fd < FD_SETSIZE; fd++) { - FD_SET(fd, &read_set); - } - // Pass a zero timeout so that select returns immediately. - struct timeval timeout = {}; - EXPECT_THAT(select(kNfds, &read_set, nullptr, nullptr, &timeout), - SyscallSucceedsWithValue(0)); -} - -// This test illustrates Linux's behavior of 'select' calls passing after -// setrlimit RLIMIT_NOFILE is called. In particular, versions of sshd rely on -// this behavior. See b/122318458. -TEST_F(SelectTest, SetrlimitCallNOFILE) { - fd_set read_set; - FD_ZERO(&read_set); - timeval timeout = {}; - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(NewTempAbsPath(), O_RDONLY | O_CREAT, S_IRUSR)); - - Cleanup reset_rlimit = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_NOFILE, 0)); - - FD_SET(fd.get(), &read_set); - // this call with zero timeout should return immediately - EXPECT_THAT(select(fd.get() + 1, &read_set, nullptr, nullptr, &timeout), - SyscallSucceeds()); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc deleted file mode 100644 index 28f51a3bf..000000000 --- a/test/syscalls/linux/semaphore.cc +++ /dev/null @@ -1,1015 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/ipc.h> -#include <sys/sem.h> -#include <sys/types.h> - -#include <atomic> -#include <cerrno> -#include <ctime> -#include <set> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "absl/memory/memory.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -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) {} - ~AutoSem() { - if (id_ >= 0) { - EXPECT_THAT(semctl(id_, 0, IPC_RMID), SyscallSucceeds()); - } - } - - int release() { - int old = id_; - id_ = -1; - return old; - } - - int get() { return id_; } - - private: - int id_ = -1; -}; - -bool operator==(struct semid_ds const& a, struct semid_ds const& b) { - return a.sem_perm.__key == b.sem_perm.__key && - a.sem_perm.uid == b.sem_perm.uid && a.sem_perm.gid == b.sem_perm.gid && - a.sem_perm.cuid == b.sem_perm.cuid && - a.sem_perm.cgid == b.sem_perm.cgid && - a.sem_perm.mode == b.sem_perm.mode && a.sem_otime == b.sem_otime && - a.sem_ctime == b.sem_ctime && a.sem_nsems == b.sem_nsems; -} - -TEST(SemaphoreTest, SemGet) { - // Test creation and lookup. - AutoSem sem(semget(1, 10, IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - EXPECT_THAT(semget(1, 10, IPC_CREAT), SyscallSucceedsWithValue(sem.get())); - EXPECT_THAT(semget(1, 9, IPC_CREAT), SyscallSucceedsWithValue(sem.get())); - - // Creation and lookup failure cases. - EXPECT_THAT(semget(1, 11, IPC_CREAT), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(semget(1, -1, IPC_CREAT), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(semget(1, 10, IPC_CREAT | IPC_EXCL), - SyscallFailsWithErrno(EEXIST)); - EXPECT_THAT(semget(2, 1, 0), SyscallFailsWithErrno(ENOENT)); - EXPECT_THAT(semget(2, 0, IPC_CREAT), SyscallFailsWithErrno(EINVAL)); - - // Private semaphores never conflict. - AutoSem sem2(semget(IPC_PRIVATE, 1, 0)); - AutoSem sem3(semget(IPC_PRIVATE, 1, 0)); - ASSERT_THAT(sem2.get(), SyscallSucceeds()); - EXPECT_NE(sem.get(), sem2.get()); - ASSERT_THAT(sem3.get(), SyscallSucceeds()); - EXPECT_NE(sem3.get(), sem2.get()); -} - -// Tests simple operations that shouldn't block in a single-thread. -TEST(SemaphoreTest, SemOpSingleNoBlock) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - - buf.sem_op = -1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - - buf.sem_op = 0; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - - // Error cases with invalid values. - ASSERT_THAT(semop(sem.get() + 1, &buf, 1), SyscallFailsWithErrno(EINVAL)); - - buf.sem_num = 1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EFBIG)); - - ASSERT_THAT(semop(sem.get(), nullptr, 0), SyscallFailsWithErrno(EINVAL)); -} - -// Tests simple timed operations that shouldn't block in a single-thread. -TEST(SemaphoreTest, SemTimedOpSingleNoBlock) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct sembuf buf = {}; - buf.sem_op = 1; - struct timespec timeout = {}; - // 50 milliseconds. - timeout.tv_nsec = 5e7; - ASSERT_THAT(semtimedop(sem.get(), &buf, 1, &timeout), SyscallSucceeds()); - - buf.sem_op = -1; - EXPECT_THAT(semtimedop(sem.get(), &buf, 1, &timeout), SyscallSucceeds()); - - buf.sem_op = 0; - EXPECT_THAT(semtimedop(sem.get(), &buf, 1, &timeout), SyscallSucceeds()); - - // Error cases with invalid values. - EXPECT_THAT(semtimedop(sem.get() + 1, &buf, 1, &timeout), - SyscallFailsWithErrno(EINVAL)); - - buf.sem_num = 1; - EXPECT_THAT(semtimedop(sem.get(), &buf, 1, &timeout), - SyscallFailsWithErrno(EFBIG)); - buf.sem_num = 0; - - EXPECT_THAT(semtimedop(sem.get(), nullptr, 0, &timeout), - SyscallFailsWithErrno(EINVAL)); - - timeout.tv_nsec = 1e9; - EXPECT_THAT(semtimedop(sem.get(), &buf, 0, &timeout), - SyscallFailsWithErrno(EINVAL)); -} - -// Tests multiple operations that shouldn't block in a single-thread. -TEST(SemaphoreTest, SemOpMultiNoBlock) { - AutoSem sem(semget(IPC_PRIVATE, 4, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct sembuf bufs[5] = {}; - bufs[0].sem_num = 0; - bufs[0].sem_op = 10; - bufs[0].sem_flg = 0; - - bufs[1].sem_num = 1; - bufs[1].sem_op = 2; - bufs[1].sem_flg = 0; - - bufs[2].sem_num = 2; - bufs[2].sem_op = 3; - bufs[2].sem_flg = 0; - - bufs[3].sem_num = 0; - bufs[3].sem_op = -5; - bufs[3].sem_flg = 0; - - bufs[4].sem_num = 2; - bufs[4].sem_op = 2; - bufs[4].sem_flg = 0; - - ASSERT_THAT(semop(sem.get(), bufs, ABSL_ARRAYSIZE(bufs)), SyscallSucceeds()); - - ASSERT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(5)); - ASSERT_THAT(semctl(sem.get(), 1, GETVAL), SyscallSucceedsWithValue(2)); - ASSERT_THAT(semctl(sem.get(), 2, GETVAL), SyscallSucceedsWithValue(5)); - ASSERT_THAT(semctl(sem.get(), 3, GETVAL), SyscallSucceedsWithValue(0)); - - for (auto& b : bufs) { - b.sem_op = -b.sem_op; - } - // 0 and 3 order must be reversed, otherwise it will block. - std::swap(bufs[0].sem_op, bufs[3].sem_op); - ASSERT_THAT(RetryEINTR(semop)(sem.get(), bufs, ABSL_ARRAYSIZE(bufs)), - SyscallSucceeds()); - - // All semaphores should be back to 0 now. - for (size_t i = 0; i < 4; ++i) { - ASSERT_THAT(semctl(sem.get(), i, GETVAL), SyscallSucceedsWithValue(0)); - } -} - -// Makes a best effort attempt to ensure that operation would block. -TEST(SemaphoreTest, SemOpBlock) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - std::atomic<int> blocked = ATOMIC_VAR_INIT(1); - ScopedThread th([&sem, &blocked] { - absl::SleepFor(absl::Milliseconds(100)); - ASSERT_EQ(blocked.load(), 1); - - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - }); - - struct sembuf buf = {}; - buf.sem_op = -1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - blocked.store(0); -} - -// Makes a best effort attempt to ensure that operation would be timeout when -// being blocked. -TEST(SemaphoreTest, SemTimedOpBlock) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - ScopedThread th([&sem] { - absl::SleepFor(absl::Milliseconds(100)); - - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - }); - - struct sembuf buf = {}; - buf.sem_op = -1; - struct timespec timeout = {}; - timeout.tv_nsec = 5e7; - // semtimedop reaches the time limit, it fails with errno EAGAIN. - ASSERT_THAT(RetryEINTR(semtimedop)(sem.get(), &buf, 1, &timeout), - SyscallFailsWithErrno(EAGAIN)); -} - -// Tests that IPC_NOWAIT returns with no wait. -TEST(SemaphoreTest, SemOpNoBlock) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct sembuf buf = {}; - buf.sem_flg = IPC_NOWAIT; - - buf.sem_op = -1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EAGAIN)); - - buf.sem_op = 1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - - buf.sem_op = 0; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EAGAIN)); -} - -// Test runs 2 threads, one signals the other waits the same number of times. -TEST(SemaphoreTest, SemOpSimple) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - constexpr size_t kLoops = 100; - ScopedThread th([&sem] { - struct sembuf buf = {}; - buf.sem_op = 1; - for (size_t i = 0; i < kLoops; i++) { - // Sleep to prevent making all increments in one shot without letting - // the waiter wait. - absl::SleepFor(absl::Milliseconds(1)); - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - } - }); - - struct sembuf buf = {}; - buf.sem_op = -1; - for (size_t i = 0; i < kLoops; i++) { - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - } -} - -// Tests that semaphore can be removed while there are waiters. -// NoRandomSave: Test relies on timing that random save throws off. -TEST(SemaphoreTest, SemOpRemoveWithWaiter_NoRandomSave) { - AutoSem sem(semget(IPC_PRIVATE, 2, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - ScopedThread th([&sem] { - absl::SleepFor(absl::Milliseconds(250)); - ASSERT_THAT(semctl(sem.release(), 0, IPC_RMID), SyscallSucceeds()); - }); - - // This must happen before IPC_RMID runs above. Otherwise it fails with EINVAL - // instead because the semaphore has already been removed. - struct sembuf buf = {}; - buf.sem_op = -1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), - SyscallFailsWithErrno(EIDRM)); -} - -// Semaphore isn't fair. It will execute any waiter that can satisfy the -// request even if it gets in front of other waiters. -TEST(SemaphoreTest, SemOpBestFitExecution) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - ScopedThread th([&sem] { - struct sembuf buf = {}; - buf.sem_op = -2; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallFails()); - // Ensure that wait will only unblock when the semaphore is removed. On - // EINTR retry it may race with deletion and return EINVAL. - ASSERT_TRUE(errno == EIDRM || errno == EINVAL) << "errno=" << errno; - }); - - // Ensures that '-1' below will unblock even though '-10' above is waiting - // for the same semaphore. - for (size_t i = 0; i < 10; ++i) { - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - - absl::SleepFor(absl::Milliseconds(10)); - - buf.sem_op = -1; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - } - - ASSERT_THAT(semctl(sem.release(), 0, IPC_RMID), SyscallSucceeds()); -} - -// Executes random operations in multiple threads and verify correctness. -TEST(SemaphoreTest, SemOpRandom) { - // Don't do cooperative S/R tests because there are too many syscalls in - // this test, - const DisableSave ds; - - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - // Protects the seed below. - absl::Mutex mutex; - uint32_t seed = time(nullptr); - - int count = 0; // Tracks semaphore value. - bool done = false; // Tells waiters to stop after signal threads are done. - - // These threads will wait in a loop. - std::unique_ptr<ScopedThread> decs[5]; - for (auto& dec : decs) { - dec = absl::make_unique<ScopedThread>([&sem, &mutex, &count, &seed, &done] { - for (size_t i = 0; i < 500; ++i) { - int16_t val; - { - absl::MutexLock l(&mutex); - if (done) { - return; - } - val = (rand_r(&seed) % 10 + 1); // Rand between 1 and 10. - count -= val; - } - struct sembuf buf = {}; - buf.sem_op = -val; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - absl::SleepFor(absl::Milliseconds(val * 2)); - } - }); - } - - // These threads will wait for zero in a loop. - std::unique_ptr<ScopedThread> zeros[5]; - for (auto& zero : zeros) { - zero = absl::make_unique<ScopedThread>([&sem, &mutex, &done] { - for (size_t i = 0; i < 500; ++i) { - { - absl::MutexLock l(&mutex); - if (done) { - return; - } - } - struct sembuf buf = {}; - buf.sem_op = 0; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - absl::SleepFor(absl::Milliseconds(10)); - } - }); - } - - // These threads will signal in a loop. - std::unique_ptr<ScopedThread> incs[5]; - for (auto& inc : incs) { - inc = absl::make_unique<ScopedThread>([&sem, &mutex, &count, &seed] { - for (size_t i = 0; i < 500; ++i) { - int16_t val; - { - absl::MutexLock l(&mutex); - val = (rand_r(&seed) % 10 + 1); // Rand between 1 and 10. - count += val; - } - struct sembuf buf = {}; - buf.sem_op = val; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - absl::SleepFor(absl::Milliseconds(val * 2)); - } - }); - } - - // First wait for signal threads to be done. - for (auto& inc : incs) { - inc->Join(); - } - - // Now there could be waiters blocked (remember operations are random). - // Notify waiters that we're done and signal semaphore just the right amount. - { - absl::MutexLock l(&mutex); - done = true; - struct sembuf buf = {}; - buf.sem_op = -count; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - } - - // Now all waiters should unblock and exit. - for (auto& dec : decs) { - dec->Join(); - } - for (auto& zero : zeros) { - zero->Join(); - } -} - -TEST(SemaphoreTest, SemOpNamespace) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - AutoSem sem(semget(123, 1, 0600 | IPC_CREAT | IPC_EXCL)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - ScopedThread([]() { - EXPECT_THAT(unshare(CLONE_NEWIPC), SyscallSucceeds()); - AutoSem sem(semget(123, 1, 0600 | IPC_CREAT | IPC_EXCL)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - }); -} - -TEST(SemaphoreTest, SemCtlVal) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - // Semaphore must start with 0. - EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(0)); - - // Increase value and ensure waiters are woken up. - ScopedThread th([&sem] { - struct sembuf buf = {}; - buf.sem_op = -10; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - }); - - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 9), SyscallSucceeds()); - EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(9)); - - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 20), SyscallSucceeds()); - const int value = semctl(sem.get(), 0, GETVAL); - // 10 or 20 because it could have raced with waiter above. - EXPECT_TRUE(value == 10 || value == 20) << "value=" << value; - th.Join(); - - // Set it back to 0 and ensure that waiters are woken up. - ScopedThread thZero([&sem] { - struct sembuf buf = {}; - buf.sem_op = 0; - ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds()); - }); - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds()); - EXPECT_THAT(semctl(sem.get(), 0, GETVAL), SyscallSucceedsWithValue(0)); - thZero.Join(); -} - -TEST(SemaphoreTest, SemCtlValAll) { - AutoSem sem(semget(IPC_PRIVATE, 3, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - // Semaphores must start with 0. - uint16_t get[3] = {10, 10, 10}; - EXPECT_THAT(semctl(sem.get(), 1, GETALL, get), SyscallSucceedsWithValue(0)); - for (auto v : get) { - EXPECT_EQ(v, 0); - } - - // SetAll and check that they were set. - uint16_t vals[3] = {0, 10, 20}; - EXPECT_THAT(semctl(sem.get(), 1, SETALL, vals), SyscallSucceedsWithValue(0)); - EXPECT_THAT(semctl(sem.get(), 1, GETALL, get), SyscallSucceedsWithValue(0)); - for (size_t i = 0; i < ABSL_ARRAYSIZE(vals); ++i) { - EXPECT_EQ(get[i], vals[i]); - } - - EXPECT_THAT(semctl(sem.get(), 1, SETALL, nullptr), - SyscallFailsWithErrno(EFAULT)); -} - -TEST(SemaphoreTest, SemCtlGetPid) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); - EXPECT_THAT(semctl(sem.get(), 0, GETPID), SyscallSucceedsWithValue(getpid())); -} - -TEST(SemaphoreTest, SemCtlGetPidFork) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - const pid_t child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(semctl(sem.get(), 0, SETVAL, 1) == 0); - TEST_PCHECK(semctl(sem.get(), 0, GETPID) == getpid()); - - _exit(0); - } - ASSERT_THAT(child_pid, SyscallSucceeds()); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << " status " << status; -} - -TEST(SemaphoreTest, SemIpcSet) { - // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); - - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct semid_ds semid = {}; - semid.sem_perm.uid = getuid(); - semid.sem_perm.gid = getgid(); - - // Make semaphore readonly and check that signal fails. - semid.sem_perm.mode = 0400; - EXPECT_THAT(semctl(sem.get(), 0, IPC_SET, &semid), SyscallSucceeds()); - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES)); - - // Make semaphore writeonly and check that wait for zero fails. - semid.sem_perm.mode = 0200; - EXPECT_THAT(semctl(sem.get(), 0, IPC_SET, &semid), SyscallSucceeds()); - buf.sem_op = 0; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES)); -} - -TEST(SemaphoreTest, SemCtlIpcStat) { - // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); - const uid_t kUid = getuid(); - const gid_t kGid = getgid(); - time_t start_time = time(nullptr); - - AutoSem sem(semget(IPC_PRIVATE, 10, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - struct semid_ds ds; - EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds()); - - EXPECT_EQ(ds.sem_perm.__key, IPC_PRIVATE); - EXPECT_EQ(ds.sem_perm.uid, kUid); - EXPECT_EQ(ds.sem_perm.gid, kGid); - EXPECT_EQ(ds.sem_perm.cuid, kUid); - EXPECT_EQ(ds.sem_perm.cgid, kGid); - EXPECT_EQ(ds.sem_perm.mode, 0600); - // Last semop time is not set on creation. - EXPECT_EQ(ds.sem_otime, 0); - EXPECT_GE(ds.sem_ctime, start_time); - EXPECT_EQ(ds.sem_nsems, 10); - - // The timestamps only have a resolution of seconds; slow down so we actually - // see the timestamps change. - absl::SleepFor(absl::Seconds(1)); - - // Set semid_ds structure of the set. - auto last_ctime = ds.sem_ctime; - start_time = time(nullptr); - struct semid_ds semid_to_set = {}; - semid_to_set.sem_perm.uid = kUid; - semid_to_set.sem_perm.gid = kGid; - semid_to_set.sem_perm.mode = 0666; - ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds()); - struct sembuf buf = {}; - buf.sem_op = 1; - ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds()); - - EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds()); - EXPECT_EQ(ds.sem_perm.mode, 0666); - EXPECT_GE(ds.sem_otime, start_time); - EXPECT_GT(ds.sem_ctime, last_ctime); - - // An invalid semid fails the syscall with errno EINVAL. - EXPECT_THAT(semctl(sem.get() + 1, 0, IPC_STAT, &ds), - SyscallFailsWithErrno(EINVAL)); - - // Make semaphore not readable and check the signal fails. - semid_to_set.sem_perm.mode = 0200; - ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds()); - EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), - SyscallFailsWithErrno(EACCES)); -} - -// Calls semctl(semid, 0, cmd) until the returned value is >= target, an -// internal timeout expires, or semctl returns an error. -PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) { - constexpr absl::Duration timeout = absl::Seconds(10); - const auto deadline = absl::Now() + timeout; - int semcnt = 0; - while (absl::Now() < deadline) { - semcnt = semctl(semid, 0, cmd); - if (semcnt < 0) { - return PosixError(errno, "semctl(GETZCNT) failed"); - } - if (semcnt >= target) { - break; - } - absl::SleepFor(absl::Milliseconds(10)); - } - return semcnt; -} - -TEST(SemaphoreTest, SemopGetzcnt) { - // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); - // Create a write only semaphore set. - AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - // No read permission to retrieve semzcnt. - EXPECT_THAT(semctl(sem.get(), 0, GETZCNT), SyscallFailsWithErrno(EACCES)); - - // Remove the calling thread's read permission. - struct semid_ds ds = {}; - ds.sem_perm.uid = getuid(); - ds.sem_perm.gid = getgid(); - ds.sem_perm.mode = 0600; - ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds()); - - std::vector<pid_t> children; - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); - - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = 0; - constexpr size_t kLoops = 10; - for (size_t i = 0; i < kLoops; i++) { - auto child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); - _exit(0); - } - children.push_back(child_pid); - } - - EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETZCNT), - IsPosixErrorOkAndHolds(kLoops)); - // Set semval to 0, which wakes up children that sleep on the semop. - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds()); - for (const auto& child_pid : children) { - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - } - EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); -} - -TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) { - auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); - ASSERT_THAT(semid, SyscallSucceeds()); - ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds()); - ASSERT_EQ(semctl(semid, 0, GETZCNT), 0); - - auto child_pid = fork(); - if (child_pid == 0) { - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = 0; - - // Ensure that wait will only unblock when the semaphore is removed. On - // EINTR retry it may race with deletion and return EINVAL. - TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 && - (errno == EIDRM || errno == EINVAL)); - _exit(0); - } - - EXPECT_THAT(WaitSemctl(semid, 1, GETZCNT), IsPosixErrorOkAndHolds(1)); - // Remove the semaphore set, which fails the sleep semop. - ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - EXPECT_THAT(semctl(semid, 0, GETZCNT), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds()); - ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0); - - // Saving will cause semop() to be spuriously interrupted. - DisableSave ds; - - auto child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR); - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = 0; - - TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR); - _exit(0); - } - - EXPECT_THAT(WaitSemctl(sem.get(), 1, GETZCNT), IsPosixErrorOkAndHolds(1)); - // Send a signal to the child, which fails the sleep semop. - ASSERT_EQ(kill(child_pid, SIGHUP), 0); - - ds.reset(); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0); -} - -TEST(SemaphoreTest, SemopGetncnt) { - // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); - // Create a write only semaphore set. - AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - - // No read permission to retrieve semzcnt. - EXPECT_THAT(semctl(sem.get(), 0, GETNCNT), SyscallFailsWithErrno(EACCES)); - - // Remove the calling thread's read permission. - struct semid_ds ds = {}; - ds.sem_perm.uid = getuid(); - ds.sem_perm.gid = getgid(); - ds.sem_perm.mode = 0600; - ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds()); - - std::vector<pid_t> children; - - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = -1; - constexpr size_t kLoops = 10; - for (size_t i = 0; i < kLoops; i++) { - auto child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); - _exit(0); - } - children.push_back(child_pid); - } - EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETNCNT), - IsPosixErrorOkAndHolds(kLoops)); - // Set semval to 1, which wakes up children that sleep on the semop. - ASSERT_THAT(semctl(sem.get(), 0, SETVAL, kLoops), SyscallSucceeds()); - for (const auto& child_pid : children) { - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - } - EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); -} - -TEST(SemaphoreTest, SemopGetncntOnSetRemoval) { - auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT); - ASSERT_THAT(semid, SyscallSucceeds()); - ASSERT_EQ(semctl(semid, 0, GETNCNT), 0); - - auto child_pid = fork(); - if (child_pid == 0) { - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = -1; - - // Ensure that wait will only unblock when the semaphore is removed. On - // EINTR retry it may race with deletion and return EINVAL - TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 && - (errno == EIDRM || errno == EINVAL)); - _exit(0); - } - - EXPECT_THAT(WaitSemctl(semid, 1, GETNCNT), IsPosixErrorOkAndHolds(1)); - // Remove the semaphore set, which fails the sleep semop. - ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - EXPECT_THAT(semctl(semid, 0, GETNCNT), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) { - AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); - ASSERT_THAT(sem.get(), SyscallSucceeds()); - ASSERT_EQ(semctl(sem.get(), 0, GETNCNT), 0); - - // Saving will cause semop() to be spuriously interrupted. - DisableSave ds; - - auto child_pid = fork(); - if (child_pid == 0) { - TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR); - struct sembuf buf = {}; - buf.sem_num = 0; - buf.sem_op = -1; - - TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR); - _exit(0); - } - EXPECT_THAT(WaitSemctl(sem.get(), 1, GETNCNT), IsPosixErrorOkAndHolds(1)); - // Send a signal to the child, which fails the sleep semop. - ASSERT_EQ(kill(child_pid, SIGHUP), 0); - - ds.reset(); - - int status; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); - EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); -} - -#ifndef SEM_STAT_ANY -#define SEM_STAT_ANY 20 -#endif // SEM_STAT_ANY - -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_TRUE(ds == ipc_stat_ds); - - // 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; - // IPC_SET command here updates sem_ctime member of the sem. - ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); - ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); - int val = semctl(i, 0, SEM_STAT_ANY, &ds); - if (val == -1) { - // Only if the kernel doesn't support the command SEM_STAT_ANY. - EXPECT_TRUE(errno == EINVAL || errno == EFAULT); - } else { - EXPECT_EQ(sem_id, val); - EXPECT_LE(ipc_stat_ds.sem_ctime, ds.sem_ctime); - ipc_stat_ds.sem_ctime = 0; - ipc_stat_ds.sem_perm.mode = 0200; - ds.sem_ctime = 0; - EXPECT_TRUE(ipc_stat_ds == ds); - } - 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_TRUE(ds == ipc_stat_ds); - - // 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; - // IPC_SET command here updates sem_ctime member of the sem. - ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); - ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); - int val = semctl(i, 0, SEM_STAT_ANY, &ds); - - if (val == -1) { - // Only if the kernel doesn't support the command SEM_STAT_ANY. - EXPECT_TRUE(errno == EINVAL || errno == EFAULT); - } else { - EXPECT_EQ(val, sem_id); - EXPECT_LE(ipc_stat_ds.sem_ctime, ds.sem_ctime); - ipc_stat_ds.sem_ctime = 0; - ipc_stat_ds.sem_perm.mode = 0200; - ds.sem_ctime = 0; - EXPECT_TRUE(ipc_stat_ds == ds); - } - 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, 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 -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc deleted file mode 100644 index 93b3a94f1..000000000 --- a/test/syscalls/linux/sendfile.cc +++ /dev/null @@ -1,729 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/unistd.h> -#include <sys/eventfd.h> -#include <sys/sendfile.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/eventfd_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/signal_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SendFileTest, SendZeroBytes) { - // Create temp files. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, 0), - SyscallSucceedsWithValue(0)); -} - -TEST(SendFileTest, InvalidOffset) { - // Create temp files. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - off_t offset = -1; - EXPECT_THAT(sendfile(outf.get(), inf.get(), &offset, 0), - SyscallFailsWithErrno(EINVAL)); -} - -int memfd_create(const std::string& name, unsigned int flags) { - return syscall(__NR_memfd_create, name.c_str(), flags); -} - -TEST(SendFileTest, Overflow) { - // Create input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file. - int fd; - EXPECT_THAT(fd = memfd_create("overflow", 0), SyscallSucceeds()); - const FileDescriptor outf(fd); - - // out_offset + kSize overflows INT64_MAX. - loff_t out_offset = 0x7ffffffffffffffeull; - constexpr int kSize = 3; - EXPECT_THAT(sendfile(outf.get(), inf.get(), &out_offset, kSize), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SendFileTest, SendTrivially) { - // Create temp files. - constexpr char kData[] = "To be, or not to be, that is the question:"; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT(bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize), - SyscallSucceedsWithValue(kDataSize)); - - // Close outf to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kDataSize)); - EXPECT_EQ(kData, absl::string_view(actual, bytes_sent)); -} - -TEST(SendFileTest, SendTriviallyWithBothFilesReadWrite) { - // Create temp files. - constexpr char kData[] = "Whether 'tis nobler in the mind to suffer"; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as readwrite. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Open the output file as readwrite. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT(bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize), - SyscallSucceedsWithValue(kDataSize)); - - // Close outf to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kDataSize)); - EXPECT_EQ(kData, absl::string_view(actual, bytes_sent)); -} - -TEST(SendFileTest, SendAndUpdateFileOffset) { - // Create temp files. - // Test input string length must be > 2 AND even. - constexpr char kData[] = "The slings and arrows of outrageous fortune,"; - constexpr int kDataSize = sizeof(kData) - 1; - constexpr int kHalfDataSize = kDataSize / 2; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - - // Close outf to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kHalfDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ(absl::string_view(kData, kHalfDataSize), - absl::string_view(actual, bytes_sent)); - - // Verify that the input file offset has been updated. - ASSERT_THAT(read(inf.get(), &actual, kDataSize - bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ( - absl::string_view(kData + kDataSize - bytes_sent, kDataSize - bytes_sent), - absl::string_view(actual, kHalfDataSize)); -} - -TEST(SendFileTest, SendToDevZeroAndUpdateFileOffset) { - // Create temp files. - // Test input string length must be > 2 AND even. - constexpr char kData[] = "The slings and arrows of outrageous fortune,"; - constexpr int kDataSize = sizeof(kData) - 1; - constexpr int kHalfDataSize = kDataSize / 2; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open /dev/zero as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - - char actual[kHalfDataSize]; - // Verify that the input file offset has been updated. - ASSERT_THAT(read(inf.get(), &actual, kDataSize - bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ( - absl::string_view(kData + kDataSize - bytes_sent, kDataSize - bytes_sent), - absl::string_view(actual, kHalfDataSize)); -} - -TEST(SendFileTest, SendAndUpdateFileOffsetFromNonzeroStartingPoint) { - // Create temp files. - // Test input string length must be > 2 AND divisible by 4. - constexpr char kData[] = "The slings and arrows of outrageous fortune,"; - constexpr int kDataSize = sizeof(kData) - 1; - constexpr int kHalfDataSize = kDataSize / 2; - constexpr int kQuarterDataSize = kHalfDataSize / 2; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Read a quarter of the data from the infile which should update the file - // offset, we don't actually care about the data so it goes into the garbage. - char garbage[kQuarterDataSize]; - ASSERT_THAT(read(inf.get(), &garbage, kQuarterDataSize), - SyscallSucceedsWithValue(kQuarterDataSize)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - - // Close out_fd to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kHalfDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ(absl::string_view(kData + kQuarterDataSize, kHalfDataSize), - absl::string_view(actual, bytes_sent)); - - // Verify that the input file offset has been updated. - ASSERT_THAT(read(inf.get(), &actual, kQuarterDataSize), - SyscallSucceedsWithValue(kQuarterDataSize)); - - EXPECT_EQ( - absl::string_view(kData + kDataSize - kQuarterDataSize, kQuarterDataSize), - absl::string_view(actual, kQuarterDataSize)); -} - -TEST(SendFileTest, SendAndUpdateGivenOffset) { - // Create temp files. - // Test input string length must be >= 4 AND divisible by 4. - constexpr char kData[] = "Or to take Arms against a Sea of troubles,"; - constexpr int kDataSize = sizeof(kData) + 1; - constexpr int kHalfDataSize = kDataSize / 2; - constexpr int kQuarterDataSize = kHalfDataSize / 2; - constexpr int kThreeFourthsDataSize = 3 * kDataSize / 4; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Create offset for sending. - off_t offset = kQuarterDataSize; - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), &offset, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - - // Close out_fd to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kHalfDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ(absl::string_view(kData + kQuarterDataSize, kHalfDataSize), - absl::string_view(actual, bytes_sent)); - - // Verify that the input file offset has NOT been updated. - ASSERT_THAT(read(inf.get(), &actual, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ(absl::string_view(kData, kHalfDataSize), - absl::string_view(actual, kHalfDataSize)); - - // Verify that the offset pointer has been updated. - EXPECT_EQ(offset, kThreeFourthsDataSize); -} - -TEST(SendFileTest, DoNotSendfileIfOutfileIsAppendOnly) { - // Create temp files. - constexpr char kData[] = "And by opposing end them: to die, to sleep"; - constexpr int kDataSize = sizeof(kData) - 1; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as append only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY | O_APPEND)); - - // Send data and verify that sendfile returns the correct errno. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SendFileTest, AppendCheckOrdering) { - constexpr char kData[] = "And by opposing end them: to die, to sleep"; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - - const FileDescriptor read = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - const FileDescriptor write = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY)); - const FileDescriptor append = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_APPEND)); - - // Check that read/write file mode is verified before append. - EXPECT_THAT(sendfile(append.get(), read.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EBADF)); - EXPECT_THAT(sendfile(write.get(), write.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EBADF)); -} - -TEST(SendFileTest, DoNotSendfileIfOutfileIsNotWritable) { - // Create temp files. - constexpr char kData[] = "No more; and by a sleep, to say we end"; - constexpr int kDataSize = sizeof(kData) - 1; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as read only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Send data and verify that sendfile returns the correct errno. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EBADF)); -} - -TEST(SendFileTest, DoNotSendfileIfInfileIsNotReadable) { - // Create temp files. - constexpr char kData[] = "the heart-ache, and the thousand natural shocks"; - constexpr int kDataSize = sizeof(kData) - 1; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as write only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_WRONLY)); - - // Open the output file as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct errno. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EBADF)); -} - -TEST(SendFileTest, DoNotSendANegativeNumberOfBytes) { - // Create temp files. - constexpr char kData[] = "that Flesh is heir to? 'Tis a consummation"; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct errno. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, -1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SendFileTest, SendTheCorrectNumberOfBytesEvenIfWeTryToSendTooManyBytes) { - // Create temp files. - constexpr char kData[] = "devoutly to be wished. To die, to sleep,"; - constexpr int kDataSize = sizeof(kData) - 1; - - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - FileDescriptor outf; - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kDataSize + 100), - SyscallSucceedsWithValue(kDataSize)); - - // Close outf to avoid leak. - outf.reset(); - - // Open the output file as read only. - outf = ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - - // Verify that the output file has the correct data. - char actual[kDataSize]; - ASSERT_THAT(read(outf.get(), &actual, bytes_sent), - SyscallSucceedsWithValue(kDataSize)); - EXPECT_EQ(kData, absl::string_view(actual, bytes_sent)); -} - -TEST(SendFileTest, SendToNotARegularFile) { - // Make temp input directory and open as read only. - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY)); - - // Make temp output file and open as write only. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Receive an error since a directory is not a regular file. - EXPECT_THAT(sendfile(outf.get(), inf.get(), nullptr, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SendFileTest, SendPipeWouldBlock) { - // Create temp file. - constexpr char kData[] = - "The fool doth think he is wise, but the wise man knows himself to be a " - "fool."; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Setup the output named pipe. - int fds[2]; - ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill up the pipe's buffer. - int pipe_size = -1; - ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds()); - std::vector<char> buf(2 * pipe_size); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(pipe_size)); - - EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, kDataSize), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST(SendFileTest, SendPipeEOF) { - // Create and open an empty input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Setup the output named pipe. - int fds[2]; - ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, 123), - SyscallSucceedsWithValue(0)); -} - -TEST(SendFileTest, SendToFullPipeReturnsEAGAIN) { - // Create and open an empty input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Set up the output pipe. - int fds[2]; - ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - int pipe_size = -1; - ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds()); - int data_size = pipe_size * 8; - ASSERT_THAT(ftruncate(in_fd.get(), data_size), SyscallSucceeds()); - - ASSERT_THAT(sendfile(wfd.get(), in_fd.get(), 0, data_size), - SyscallSucceeds()); - EXPECT_THAT(sendfile(wfd.get(), in_fd.get(), 0, data_size), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(SendFileTest, SendPipeBlocks) { - // Create temp file. - constexpr char kData[] = - "The fault, dear Brutus, is not in our stars, but in ourselves."; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Setup the output named pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill up the pipe's buffer. - int pipe_size = -1; - ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds()); - std::vector<char> buf(pipe_size); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(pipe_size)); - - ScopedThread t([&]() { - absl::SleepFor(absl::Milliseconds(100)); - ASSERT_THAT(read(rfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(pipe_size)); - }); - - EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, kDataSize), - SyscallSucceedsWithValue(kDataSize)); -} - -TEST(SendFileTest, SendToSpecialFile) { - // Create temp file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - constexpr int kSize = 0x7ff; - ASSERT_THAT(ftruncate(inf.get(), kSize), SyscallSucceeds()); - - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - - // eventfd can accept a number of bytes which is a multiple of 8. - EXPECT_THAT(sendfile(eventfd.get(), inf.get(), nullptr, 0xfffff), - SyscallSucceedsWithValue(kSize & (~7))); -} - -TEST(SendFileTest, SendFileToPipe) { - // Create temp file. - constexpr char kData[] = "<insert-quote-here>"; - constexpr int kDataSize = sizeof(kData) - 1; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Create a pipe for sending to a pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Expect to read up to the given size. - std::vector<char> buf(kDataSize); - ScopedThread t([&]() { - absl::SleepFor(absl::Milliseconds(100)); - ASSERT_THAT(read(rfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kDataSize)); - }); - - // Send with twice the size of the file, which should hit EOF. - EXPECT_THAT(sendfile(wfd.get(), inf.get(), nullptr, kDataSize * 2), - SyscallSucceedsWithValue(kDataSize)); -} - -TEST(SendFileTest, SendFileToSelf_NoRandomSave) { - int rawfd; - ASSERT_THAT(rawfd = memfd_create("memfd", 0), SyscallSucceeds()); - const FileDescriptor fd(rawfd); - - char c = 0x01; - ASSERT_THAT(WriteFd(fd.get(), &c, 1), SyscallSucceedsWithValue(1)); - - // Arbitrarily chosen to make sendfile() take long enough that the sentry - // watchdog usually fires unless it's reset by sendfile() between iterations - // of the buffered copy. See b/172076632. - constexpr size_t kSendfileSize = 0xa00000; - - off_t offset = 0; - ASSERT_THAT(sendfile(fd.get(), fd.get(), &offset, kSendfileSize), - SyscallSucceedsWithValue(kSendfileSize)); -} - -static volatile int signaled = 0; -void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } - -TEST(SendFileTest, ToEventFDDoesNotSpin_NoRandomSave) { - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - - // Write the maximum value of an eventfd to a file. - const uint64_t kMaxEventfdValue = 0xfffffffffffffffe; - const auto tempfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto tempfd = ASSERT_NO_ERRNO_AND_VALUE(Open(tempfile.path(), O_RDWR)); - ASSERT_THAT( - pwrite(tempfd.get(), &kMaxEventfdValue, sizeof(kMaxEventfdValue), 0), - SyscallSucceedsWithValue(sizeof(kMaxEventfdValue))); - - // Set the eventfd's value to 1. - const uint64_t kOne = 1; - ASSERT_THAT(write(efd.get(), &kOne, sizeof(kOne)), - SyscallSucceedsWithValue(sizeof(kOne))); - - // Set up signal handler. - struct sigaction sa = {}; - sa.sa_sigaction = SigUsr1Handler; - sa.sa_flags = SA_SIGINFO; - const auto cleanup_sigact = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - // Send SIGUSR1 to this thread in 1 second. - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = SIGUSR1; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(absl::Seconds(1)); - DisableSave ds; // Asserting an EINTR. - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // Sendfile from tempfd to the eventfd. Since the eventfd is not already at - // its maximum value, the eventfd is "ready for writing"; however, since the - // eventfd's existing value plus the new value would exceed the maximum, the - // write should internally fail with EWOULDBLOCK. In this case, sendfile() - // should block instead of spinning, and eventually be interrupted by our - // timer. See b/172075629. - EXPECT_THAT( - sendfile(efd.get(), tempfd.get(), nullptr, sizeof(kMaxEventfdValue)), - SyscallFailsWithErrno(EINTR)); - - // Signal should have been handled. - EXPECT_EQ(signaled, 1); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sendfile_socket.cc b/test/syscalls/linux/sendfile_socket.cc deleted file mode 100644 index c101fe9d2..000000000 --- a/test/syscalls/linux/sendfile_socket.cc +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <sys/sendfile.h> -#include <sys/socket.h> -#include <unistd.h> - -#include <iostream> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { -namespace { - -class SendFileTest : public ::testing::TestWithParam<int> { - protected: - PosixErrorOr<std::unique_ptr<SocketPair>> Sockets(int type) { - // Bind a server socket. - int family = GetParam(); - switch (family) { - case AF_INET: { - if (type == SOCK_STREAM) { - return SocketPairKind{ - "TCP", AF_INET, type, 0, - TCPAcceptBindSocketPairCreator(AF_INET, type, 0, false)} - .Create(); - } else { - return SocketPairKind{ - "UDP", AF_INET, type, 0, - UDPBidirectionalBindSocketPairCreator(AF_INET, type, 0, false)} - .Create(); - } - } - case AF_UNIX: { - if (type == SOCK_STREAM) { - return SocketPairKind{ - "UNIX", AF_UNIX, type, 0, - FilesystemAcceptBindSocketPairCreator(AF_UNIX, type, 0)} - .Create(); - } else { - return SocketPairKind{ - "UNIX", AF_UNIX, type, 0, - FilesystemBidirectionalBindSocketPairCreator(AF_UNIX, type, 0)} - .Create(); - } - } - default: - return PosixError(EINVAL); - } - } -}; - -// Sends large file to exercise the path that read and writes data multiple -// times, esp. when more data is read than can be written. -TEST_P(SendFileTest, SendMultiple) { - std::vector<char> data(5 * 1024 * 1024); - RandomizeBuffer(data.data(), data.size()); - - // Create temp files. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()), - TempPath::kDefaultFileMode)); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Create sockets. - auto socks = ASSERT_NO_ERRNO_AND_VALUE(Sockets(SOCK_STREAM)); - - // Thread that reads data from socket and dumps to a file. - ScopedThread th([&] { - FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Read until socket is closed. - char buf[10240]; - for (int cnt = 0;; cnt++) { - int r = RetryEINTR(read)(socks->first_fd(), buf, sizeof(buf)); - // We cannot afford to save on every read() call. - if (cnt % 1000 == 0) { - ASSERT_THAT(r, SyscallSucceeds()); - } else { - const DisableSave ds; - ASSERT_THAT(r, SyscallSucceeds()); - } - if (r == 0) { - // EOF - break; - } - int w = RetryEINTR(write)(outf.get(), buf, r); - // We cannot afford to save on every write() call. - if (cnt % 1010 == 0) { - ASSERT_THAT(w, SyscallSucceedsWithValue(r)); - } else { - const DisableSave ds; - ASSERT_THAT(w, SyscallSucceedsWithValue(r)); - } - } - }); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - int cnt = 0; - for (size_t sent = 0; sent < data.size(); cnt++) { - const size_t remain = data.size() - sent; - std::cout << "sendfile, size=" << data.size() << ", sent=" << sent - << ", remain=" << remain << std::endl; - - // Send data and verify that sendfile returns the correct value. - int res = sendfile(socks->second_fd(), inf.get(), nullptr, remain); - // We cannot afford to save on every sendfile() call. - if (cnt % 120 == 0) { - MaybeSave(); - } - if (res == 0) { - // EOF - break; - } - if (res > 0) { - sent += res; - } else { - ASSERT_TRUE(errno == EINTR || errno == EAGAIN) << "errno=" << errno; - } - } - - // Close socket to stop thread. - close(socks->release_second_fd()); - th.Join(); - - // Verify that the output file has the correct data. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY)); - std::vector<char> actual(data.size(), '\0'); - ASSERT_THAT(RetryEINTR(read)(outf.get(), actual.data(), actual.size()), - SyscallSucceedsWithValue(actual.size())); - ASSERT_EQ(memcmp(data.data(), actual.data(), data.size()), 0); -} - -TEST_P(SendFileTest, Shutdown) { - // Create a socket. - auto socks = ASSERT_NO_ERRNO_AND_VALUE(Sockets(SOCK_STREAM)); - - // If this is a TCP socket, then turn off linger. - if (GetParam() == AF_INET) { - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 0; - ASSERT_THAT( - setsockopt(socks->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - } - - // Create a 1m file with random data. - std::vector<char> data(1024 * 1024); - RandomizeBuffer(data.data(), data.size()); - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()), - TempPath::kDefaultFileMode)); - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Read some data, then shutdown the socket. We don't actually care about - // checking the contents (other tests do that), so we just re-use the same - // buffer as above. - ScopedThread t([&]() { - size_t done = 0; - while (done < data.size()) { - int n = RetryEINTR(read)(socks->first_fd(), data.data(), data.size()); - ASSERT_THAT(n, SyscallSucceeds()); - done += n; - } - // Close the server side socket. - close(socks->release_first_fd()); - }); - - // Continuously stream from the file to the socket. Note we do not assert - // that a specific amount of data has been written at any time, just that some - // data is written. Eventually, we should get a connection reset error. - while (1) { - off_t offset = 0; // Always read from the start. - int n = sendfile(socks->second_fd(), inf.get(), &offset, data.size()); - EXPECT_THAT(n, AnyOf(SyscallFailsWithErrno(ECONNRESET), - SyscallFailsWithErrno(EPIPE), SyscallSucceeds())); - if (n <= 0) { - break; - } - } -} - -TEST_P(SendFileTest, SendpageFromEmptyFileToUDP) { - auto socks = ASSERT_NO_ERRNO_AND_VALUE(Sockets(SOCK_DGRAM)); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - - // The value to the count argument has to be so that it is impossible to - // allocate a buffer of this size. In Linux, sendfile transfer at most - // 0x7ffff000 (MAX_RW_COUNT) bytes. - EXPECT_THAT(sendfile(socks->first_fd(), fd.get(), 0x0, 0x8000000000004), - SyscallSucceedsWithValue(0)); -} - -INSTANTIATE_TEST_SUITE_P(AddressFamily, SendFileTest, - ::testing::Values(AF_UNIX, AF_INET)); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/setgid.cc b/test/syscalls/linux/setgid.cc deleted file mode 100644 index 163242ace..000000000 --- a/test/syscalls/linux/setgid.cc +++ /dev/null @@ -1,413 +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. - -#include <limits.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -ABSL_FLAG(std::vector<std::string>, groups, std::vector<std::string>({}), - "groups the test can use"); - -constexpr gid_t kNobody = 65534; - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kDirmodeMask = 07777; -constexpr int kDirmodeSgid = S_ISGID | 0777; -constexpr int kDirmodeNoExec = S_ISGID | 0767; -constexpr int kDirmodeNoSgid = 0777; - -// Sets effective GID and returns a Cleanup that restores the original. -PosixErrorOr<Cleanup> Setegid(gid_t egid) { - gid_t old_gid = getegid(); - if (setegid(egid) < 0) { - return PosixError(errno, absl::StrFormat("setegid(%d)", egid)); - } - return Cleanup( - [old_gid]() { EXPECT_THAT(setegid(old_gid), SyscallSucceeds()); }); -} - -// Returns a pair of groups that the user is a member of. -PosixErrorOr<std::pair<gid_t, gid_t>> Groups() { - // Were we explicitly passed GIDs? - std::vector<std::string> flagged_groups = absl::GetFlag(FLAGS_groups); - if (flagged_groups.size() >= 2) { - int group1; - int group2; - if (!absl::SimpleAtoi(flagged_groups[0], &group1) || - !absl::SimpleAtoi(flagged_groups[1], &group2)) { - return PosixError(EINVAL, "failed converting group flags to ints"); - } - return std::pair<gid_t, gid_t>(group1, group2); - } - - // See whether the user is a member of at least 2 groups. - std::vector<gid_t> groups(64); - for (; groups.size() <= NGROUPS_MAX; groups.resize(groups.size() * 2)) { - int ngroups = getgroups(groups.size(), groups.data()); - if (ngroups < 0 && errno == EINVAL) { - // Need a larger list. - continue; - } - if (ngroups < 0) { - return PosixError(errno, absl::StrFormat("getgroups(%d, %p)", - groups.size(), groups.data())); - } - - if (ngroups < 2) { - // There aren't enough groups. - break; - } - - // TODO(b/181878080): Read /proc/sys/fs/overflowgid once it is supported in - // gVisor. - if (groups[0] == kNobody || groups[1] == kNobody) { - // These groups aren't mapped into our user namespace, so we can't use - // them. - break; - } - return std::pair<gid_t, gid_t>(groups[0], groups[1]); - } - - // If we're running in gVisor and are root in the root user namespace, we can - // set our GID to whatever we want. Try that before giving up. - // - // This won't work in native tests, as despite having CAP_SETGID, the gofer - // process will be sandboxed and unable to change file GIDs. - if (!IsRunningOnGvisor()) { - return PosixError(EPERM, "no valid groups for native testing"); - } - PosixErrorOr<bool> capable = HaveCapability(CAP_SETGID); - if (!capable.ok()) { - return capable.error(); - } - if (!capable.ValueOrDie()) { - return PosixError(EPERM, "missing CAP_SETGID"); - } - gid_t gid = getegid(); - auto cleanup1 = Setegid(gid); - if (!cleanup1.ok()) { - return cleanup1.error(); - } - auto cleanup2 = Setegid(kNobody); - if (!cleanup2.ok()) { - return cleanup2.error(); - } - return std::pair<gid_t, gid_t>(gid, kNobody); -} - -class SetgidDirTest : public ::testing::Test { - protected: - void SetUp() override { - original_gid_ = getegid(); - - SKIP_IF(IsRunningWithVFS1()); - - temp_dir_ = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); - - // If we can't find two usable groups, we're in an unsupporting environment. - // Skip the test. - PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); - SKIP_IF(!groups.ok()); - groups_ = groups.ValueOrDie(); - } - - void TearDown() override { - EXPECT_THAT(setegid(original_gid_), SyscallSucceeds()); - } - - void MkdirAsGid(gid_t gid, const std::string& path, mode_t mode) { - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(gid)); - ASSERT_THAT(mkdir(path.c_str(), mode), SyscallSucceeds()); - } - - PosixErrorOr<struct stat> Stat(const std::string& path) { - struct stat stats; - if (stat(path.c_str(), &stats) < 0) { - return PosixError(errno, absl::StrFormat("stat(%s, _)", path)); - } - return stats; - } - - PosixErrorOr<struct stat> Stat(const FileDescriptor& fd) { - struct stat stats; - if (fstat(fd.get(), &stats) < 0) { - return PosixError(errno, "fstat(_, _)"); - } - return stats; - } - - TempPath temp_dir_; - std::pair<gid_t, gid_t> groups_; - gid_t original_gid_; -}; - -// The control test. Files created with a given GID are owned by that group. -TEST_F(SetgidDirTest, Control) { - // Set group to G1 and create a directory. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, 0777)); - - // Set group to G2, create a file in g1owned, and confirm that G2 owns it. - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(g1owned, "g2owned").c_str(), O_CREAT | O_RDWR, 0777)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.second); -} - -// Setgid directories cause created files to inherit GID. -TEST_F(SetgidDirTest, CreateFile) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); - - // Set group to G2, create a file, and confirm that G1 owns it. - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.first); -} - -// Setgid directories cause created directories to inherit GID. -TEST_F(SetgidDirTest, CreateDir) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); - - // Set group to G2, create a directory, confirm that G1 owns it, and that the - // setgid bit is enabled. - auto g2created = JoinPath(g1owned, "g2created"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); - EXPECT_EQ(stats.st_gid, groups_.first); - EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); -} - -// Setgid directories with group execution disabled still cause GID inheritance. -TEST_F(SetgidDirTest, NoGroupExec) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); - - // Set group to G2, create a directory, confirm that G2 owns it, and that the - // setgid bit is enabled. - auto g2created = JoinPath(g1owned, "g2created"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); - EXPECT_EQ(stats.st_gid, groups_.first); - EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); -} - -// Setting the setgid bit on directories with an existing file does not change -// the file's group. -TEST_F(SetgidDirTest, OldFile) { - // Set group to G1 and create a directory. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); - - // Set group to G2, create a file, confirm that G2 owns it. - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.second); - - // Enable setgid. - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); - - // Confirm that the file's group is still G2. - stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.second); -} - -// Setting the setgid bit on directories with an existing subdirectory does not -// change the subdirectory's group. -TEST_F(SetgidDirTest, OldDir) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); - - // Set group to G2, create a directory, confirm that G2 owns it. - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); - auto g2created = JoinPath(g1owned, "g2created"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); - EXPECT_EQ(stats.st_gid, groups_.second); - - // Enable setgid. - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); - - // Confirm that the file's group is still G2. - stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); - EXPECT_EQ(stats.st_gid, groups_.second); -} - -// Chowning a file clears the setgid and setuid bits. -TEST_F(SetgidDirTest, ChownFileClears) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(g1owned, "newfile").c_str(), O_CREAT | O_RDWR, 0666)); - ASSERT_THAT(fchmod(fd.get(), 0777 | S_ISUID | S_ISGID), SyscallSucceeds()); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.first); - EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); - - // Change the owning group. - ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); - - // The setgid and setuid bits should be cleared. - stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.second); - EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), 0); -} - -// Chowning a file with setgid enabled, but not the group exec bit, does not -// clear the setgid bit. Such files are mandatory locked. -TEST_F(SetgidDirTest, ChownNoExecFileDoesNotClear) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Open(JoinPath(g1owned, "newdir").c_str(), O_CREAT | O_RDWR, 0666)); - ASSERT_THAT(fchmod(fd.get(), 0766 | S_ISUID | S_ISGID), SyscallSucceeds()); - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.first); - EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); - - // Change the owning group. - ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); - - // Only the setuid bit is cleared. - stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); - EXPECT_EQ(stats.st_gid, groups_.second); - EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISGID); -} - -// Chowning a directory with setgid enabled does not clear the bit. -TEST_F(SetgidDirTest, ChownDirDoesNotClear) { - // Set group to G1, create a directory, and enable setgid. - auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); - ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); - ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); - - // Change the owning group. - ASSERT_THAT(chown(g1owned.c_str(), -1, groups_.second), SyscallSucceeds()); - - struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g1owned)); - EXPECT_EQ(stats.st_gid, groups_.second); - EXPECT_EQ(stats.st_mode & kDirmodeMask, kDirmodeMask); -} - -struct FileModeTestcase { - std::string name; - mode_t mode; - mode_t result_mode; - - FileModeTestcase(const std::string& name, mode_t mode, mode_t result_mode) - : name(name), mode(mode), result_mode(result_mode) {} -}; - -class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {}; - -TEST_P(FileModeTest, WriteToFile) { - SKIP_IF(IsRunningWithVFS1()); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); - auto path = JoinPath(temp_dir.path(), GetParam().name); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); - ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); - struct stat stats; - ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); - EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); - - // For security reasons, writing to the file clears the SUID bit, and clears - // the SGID bit when the group executable bit is unset (which is not a true - // SGID binary). - constexpr char kInput = 'M'; - ASSERT_THAT(write(fd.get(), &kInput, sizeof(kInput)), - SyscallSucceedsWithValue(sizeof(kInput))); - - ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); - EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); -} - -TEST_P(FileModeTest, TruncateFile) { - SKIP_IF(IsRunningWithVFS1()); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); - auto path = JoinPath(temp_dir.path(), GetParam().name); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); - ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); - struct stat stats; - ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); - EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); - - // Write something to the file, as truncating an empty file is a no-op. - constexpr char c = 'M'; - ASSERT_THAT(write(fd.get(), &c, sizeof(c)), - SyscallSucceedsWithValue(sizeof(c))); - - // For security reasons, truncating the file clears the SUID bit, and clears - // the SGID bit when the group executable bit is unset (which is not a true - // SGID binary). - ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); - - ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); - EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); -} - -INSTANTIATE_TEST_SUITE_P( - FileModes, FileModeTest, - ::testing::ValuesIn<FileModeTestcase>( - {FileModeTestcase("normal file", 0777, 0777), - FileModeTestcase("setuid", S_ISUID | 0777, 00777), - FileModeTestcase("setgid", S_ISGID | 0777, 00777), - FileModeTestcase("setuid and setgid", S_ISUID | S_ISGID | 0777, 00777), - FileModeTestcase("setgid without exec", S_ISGID | 0767, - S_ISGID | 0767), - FileModeTestcase("setuid and setgid without exec", - S_ISGID | S_ISUID | 0767, S_ISGID | 0767)})); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/shm.cc b/test/syscalls/linux/shm.cc deleted file mode 100644 index baf794152..000000000 --- a/test/syscalls/linux/shm.cc +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdio.h> -#include <sys/ipc.h> -#include <sys/mman.h> -#include <sys/shm.h> -#include <sys/types.h> - -#include "absl/time/clock.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -using ::testing::_; -using ::testing::AnyOf; -using ::testing::Eq; - -const uint64_t kAllocSize = kPageSize * 128ULL; - -PosixErrorOr<char*> Shmat(int shmid, const void* shmaddr, int shmflg) { - const intptr_t addr = - reinterpret_cast<intptr_t>(shmat(shmid, shmaddr, shmflg)); - if (addr == -1) { - return PosixError(errno, "shmat() failed"); - } - return reinterpret_cast<char*>(addr); -} - -PosixError Shmdt(const char* shmaddr) { - const int ret = shmdt(shmaddr); - if (ret == -1) { - return PosixError(errno, "shmdt() failed"); - } - return NoError(); -} - -template <typename T> -PosixErrorOr<int> Shmctl(int shmid, int cmd, T* buf) { - int ret = shmctl(shmid, cmd, reinterpret_cast<struct shmid_ds*>(buf)); - if (ret == -1) { - return PosixError(errno, "shmctl() failed"); - } - return ret; -} - -// ShmSegment is a RAII object for automatically cleaning up shm segments. -class ShmSegment { - public: - explicit ShmSegment(int id) : id_(id) {} - - ~ShmSegment() { - if (id_ >= 0) { - EXPECT_NO_ERRNO(Rmid()); - id_ = -1; - } - } - - ShmSegment(ShmSegment&& other) : id_(other.release()) {} - - ShmSegment& operator=(ShmSegment&& other) { - id_ = other.release(); - return *this; - } - - ShmSegment(ShmSegment const& other) = delete; - ShmSegment& operator=(ShmSegment const& other) = delete; - - int id() const { return id_; } - - int release() { - int id = id_; - id_ = -1; - return id; - } - - PosixErrorOr<int> Rmid() { - RETURN_IF_ERRNO(Shmctl<void>(id_, IPC_RMID, nullptr)); - return release(); - } - - private: - int id_ = -1; -}; - -PosixErrorOr<int> ShmgetRaw(key_t key, size_t size, int shmflg) { - int id = shmget(key, size, shmflg); - if (id == -1) { - return PosixError(errno, "shmget() failed"); - } - return id; -} - -PosixErrorOr<ShmSegment> Shmget(key_t key, size_t size, int shmflg) { - ASSIGN_OR_RETURN_ERRNO(int id, ShmgetRaw(key, size, shmflg)); - return ShmSegment(id); -} - -TEST(ShmTest, AttachDetach) { - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - EXPECT_EQ(attr.shm_segsz, kAllocSize); - EXPECT_EQ(attr.shm_nattch, 0); - - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - EXPECT_EQ(attr.shm_nattch, 1); - - const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - EXPECT_EQ(attr.shm_nattch, 2); - - ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - EXPECT_EQ(attr.shm_nattch, 1); - - ASSERT_NO_ERRNO(Shmdt(addr2)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - EXPECT_EQ(attr.shm_nattch, 0); -} - -TEST(ShmTest, LookupByKey) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - const ShmSegment shm = - ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - const int id2 = ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize, 0777)); - EXPECT_EQ(shm.id(), id2); -} - -TEST(ShmTest, DetachedSegmentsPersist) { - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - addr[0] = 'x'; - ASSERT_NO_ERRNO(Shmdt(addr)); - - // We should be able to re-attach to the same segment and get our data back. - addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - EXPECT_EQ(addr[0], 'x'); - ASSERT_NO_ERRNO(Shmdt(addr)); -} - -TEST(ShmTest, MultipleDetachFails) { - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - ASSERT_NO_ERRNO(Shmdt(addr)); - EXPECT_THAT(Shmdt(addr), PosixErrorIs(EINVAL, _)); -} - -TEST(ShmTest, IpcStat) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - - const time_t start = time(nullptr); - - const ShmSegment shm = - ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - - const uid_t uid = getuid(); - const gid_t gid = getgid(); - const pid_t pid = getpid(); - - struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - - EXPECT_EQ(attr.shm_perm.__key, key); - EXPECT_EQ(attr.shm_perm.uid, uid); - EXPECT_EQ(attr.shm_perm.gid, gid); - EXPECT_EQ(attr.shm_perm.cuid, uid); - EXPECT_EQ(attr.shm_perm.cgid, gid); - EXPECT_EQ(attr.shm_perm.mode, 0777); - - EXPECT_EQ(attr.shm_segsz, kAllocSize); - - EXPECT_EQ(attr.shm_atime, 0); - EXPECT_EQ(attr.shm_dtime, 0); - - // Change time is set on creation. - EXPECT_GE(attr.shm_ctime, start); - - EXPECT_EQ(attr.shm_cpid, pid); - EXPECT_EQ(attr.shm_lpid, 0); - - EXPECT_EQ(attr.shm_nattch, 0); - - // The timestamps only have a resolution of seconds; slow down so we actually - // see the timestamps change. - absl::SleepFor(absl::Seconds(1)); - const time_t pre_attach = time(nullptr); - - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - - EXPECT_GE(attr.shm_atime, pre_attach); - EXPECT_EQ(attr.shm_dtime, 0); - EXPECT_LT(attr.shm_ctime, pre_attach); - EXPECT_EQ(attr.shm_lpid, pid); - EXPECT_EQ(attr.shm_nattch, 1); - - absl::SleepFor(absl::Seconds(1)); - const time_t pre_detach = time(nullptr); - - ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - - EXPECT_LT(attr.shm_atime, pre_detach); - EXPECT_GE(attr.shm_dtime, pre_detach); - EXPECT_LT(attr.shm_ctime, pre_detach); - EXPECT_EQ(attr.shm_lpid, pid); - EXPECT_EQ(attr.shm_nattch, 0); -} - -TEST(ShmTest, ShmStat) { - // This test relies on the segment we create to be the first one on the - // system, causing it to occupy slot 1. We can't reasonably expect this on a - // general Linux host. - SKIP_IF(!IsRunningOnGvisor()); - - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(1, SHM_STAT, &attr)); - // This does the same thing as IPC_STAT, so only test that the syscall - // succeeds here. -} - -TEST(ShmTest, IpcInfo) { - struct shminfo info; - ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info)); - - EXPECT_EQ(info.shmmin, 1); // This is always 1, according to the man page. - EXPECT_GT(info.shmmax, info.shmmin); - EXPECT_GT(info.shmmni, 0); - EXPECT_GT(info.shmseg, 0); - EXPECT_GT(info.shmall, 0); -} - -TEST(ShmTest, ShmInfo) { - // Take a snapshot of the system before the test runs. - struct shm_info snap; - ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &snap)); - - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - - struct shm_info info; - ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info)); - - // We generally can't know what other processes on a linux machine do with - // shared memory segments, so we can't test specific numbers on Linux. When - // running under gvisor, we're guaranteed to be the only ones using shm, so - // we can easily verify machine-wide numbers. - if (IsRunningOnGvisor()) { - ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info)); - EXPECT_EQ(info.used_ids, snap.used_ids + 1); - EXPECT_EQ(info.shm_tot, snap.shm_tot + (kAllocSize / kPageSize)); - EXPECT_EQ(info.shm_rss, snap.shm_rss + (kAllocSize / kPageSize)); - EXPECT_EQ(info.shm_swp, 0); // Gvisor currently never swaps. - } - - ASSERT_NO_ERRNO(Shmdt(addr)); -} - -TEST(ShmTest, ShmCtlSet) { - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - - struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - ASSERT_EQ(attr.shm_perm.mode, 0777); - - attr.shm_perm.mode = 0766; - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_SET, &attr)); - - ASSERT_NO_ERRNO(Shmctl(shm.id(), IPC_STAT, &attr)); - ASSERT_EQ(attr.shm_perm.mode, 0766); - - ASSERT_NO_ERRNO(Shmdt(addr)); -} - -TEST(ShmTest, RemovedSegmentsAreMarkedDeleted) { - ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid()); - struct shmid_ds attr; - ASSERT_NO_ERRNO(Shmctl(id, IPC_STAT, &attr)); - EXPECT_NE(attr.shm_perm.mode & SHM_DEST, 0); - ASSERT_NO_ERRNO(Shmdt(addr)); -} - -TEST(ShmTest, RemovedSegmentsAreDestroyed) { - ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - - const uint64_t alloc_pages = kAllocSize / kPageSize; - - struct shm_info info; - ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info)); - const uint64_t before = info.shm_tot; - - ASSERT_NO_ERRNO(shm.Rmid()); - ASSERT_NO_ERRNO(Shmdt(addr)); - - ASSERT_NO_ERRNO(Shmctl(0 /*ignored*/, SHM_INFO, &info)); - if (IsRunningOnGvisor()) { - // No guarantees on system-wide shm memory usage on a generic linux host. - const uint64_t after = info.shm_tot; - EXPECT_EQ(after, before - alloc_pages); - } -} - -TEST(ShmTest, AllowsAttachToRemovedSegmentWithRefs) { - ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - const int id = ASSERT_NO_ERRNO_AND_VALUE(shm.Rmid()); - const char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id, nullptr, 0)); - ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmdt(addr2)); -} - -TEST(ShmTest, RemovedSegmentsAreNotDiscoverable) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - ShmSegment shm = - ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - ASSERT_NO_ERRNO(shm.Rmid()); - EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _)); -} - -TEST(ShmDeathTest, ReadonlySegment) { - SetupGvisorDeathTest(); - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, SHM_RDONLY)); - // Reading succeeds. - static_cast<void>(addr[0]); - // Writing fails. - EXPECT_EXIT(addr[0] = 'x', ::testing::KilledBySignal(SIGSEGV), ""); -} - -TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) { - // This test is susceptible to races with concurrent mmaps running in parallel - // gtest threads since the test relies on the address freed during a shm - // segment destruction to remain unused. We run the test body in a forked - // child to guarantee a single-threaded context to avoid this. - - SetupGvisorDeathTest(); - - const auto rest = [&] { - ShmSegment shm = TEST_CHECK_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = TEST_CHECK_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - - // Mark the segment as destroyed so it's automatically cleaned up when we - // crash below. We can't rely on the standard cleanup since the destructor - // will not run after the SIGSEGV. Note that this doesn't destroy the - // segment immediately since we're still attached to it. - TEST_CHECK_NO_ERRNO(shm.Rmid()); - - addr[0] = 'x'; - TEST_CHECK_NO_ERRNO(Shmdt(addr)); - - // This access should cause a SIGSEGV. - addr[0] = 'x'; - }; - - EXPECT_THAT(InForkedProcess(rest), - IsPosixErrorOkAndHolds(AnyOf(Eq(W_EXITCODE(0, SIGSEGV)), - Eq(W_EXITCODE(0, 128 + SIGSEGV))))); -} - -TEST(ShmTest, RequestingSegmentSmallerThanSHMMINFails) { - struct shminfo info; - ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info)); - const uint64_t size = info.shmmin - 1; - EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777), - PosixErrorIs(EINVAL, _)); -} - -TEST(ShmTest, RequestingSegmentLargerThanSHMMAXFails) { - struct shminfo info; - ASSERT_NO_ERRNO(Shmctl(0, IPC_INFO, &info)); - const uint64_t size = info.shmmax + kPageSize; - EXPECT_THAT(Shmget(IPC_PRIVATE, size, IPC_CREAT | 0777), - PosixErrorIs(EINVAL, _)); -} - -TEST(ShmTest, RequestingUnalignedSizeSucceeds) { - EXPECT_NO_ERRNO(Shmget(IPC_PRIVATE, 4097, IPC_CREAT | 0777)); -} - -TEST(ShmTest, RequestingDuplicateCreationFails) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777)); - EXPECT_THAT(Shmget(key, kAllocSize, IPC_CREAT | IPC_EXCL | 0777), - PosixErrorIs(EEXIST, _)); -} - -TEST(ShmTest, NonExistentSegmentsAreNotFound) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - // Do not request creation. - EXPECT_THAT(Shmget(key, kAllocSize, 0777), PosixErrorIs(ENOENT, _)); -} - -TEST(ShmTest, SegmentsSizeFixedOnCreation) { - const TempPath keyfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const key_t key = ftok(keyfile.path().c_str(), 1); - - // Base segment. - const ShmSegment shm = - ASSERT_NO_ERRNO_AND_VALUE(Shmget(key, kAllocSize, IPC_CREAT | 0777)); - - // Ask for the same segment at half size. This succeeds. - const int id2 = - ASSERT_NO_ERRNO_AND_VALUE(ShmgetRaw(key, kAllocSize / 2, 0777)); - - // Ask for the same segment at double size. - EXPECT_THAT(Shmget(key, kAllocSize * 2, 0777), PosixErrorIs(EINVAL, _)); - - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(id2, nullptr, 0)); - - // We have 2 different maps... - EXPECT_NE(addr, addr2); - - // ... And both maps are kAllocSize bytes; despite asking for a half-sized - // segment for the second map. - addr[kAllocSize - 1] = 'x'; - addr2[kAllocSize - 1] = 'x'; - - ASSERT_NO_ERRNO(Shmdt(addr)); - ASSERT_NO_ERRNO(Shmdt(addr2)); -} - -TEST(ShmTest, PartialUnmap) { - const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - EXPECT_THAT(munmap(addr + (kAllocSize / 4), kAllocSize / 2), - SyscallSucceeds()); - ASSERT_NO_ERRNO(Shmdt(addr)); -} - -// Check that sentry does not panic when asked for a zero-length private shm -// segment. Regression test for b/110694797. -TEST(ShmTest, GracefullyFailOnZeroLenSegmentCreation) { - EXPECT_THAT(Shmget(IPC_PRIVATE, 0, 0), PosixErrorIs(EINVAL, _)); -} - -TEST(ShmTest, NoDestructionOfAttachedSegmentWithMultipleRmid) { - ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( - Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - char* addr2 = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); - - // There should be 2 refs to the segment from the 2 attachments, and a single - // self-reference. Mark the segment as destroyed more than 3 times through - // shmctl(RMID). If there's a bug with the ref counting, this should cause the - // count to drop to zero. - int id = shm.release(); - for (int i = 0; i < 6; ++i) { - ASSERT_NO_ERRNO(Shmctl<void>(id, IPC_RMID, nullptr)); - } - - // Segment should remain accessible. - addr[0] = 'x'; - ASSERT_NO_ERRNO(Shmdt(addr)); - - // Segment should remain accessible even after one of the two attachments are - // detached. - addr2[0] = 'x'; - ASSERT_NO_ERRNO(Shmdt(addr2)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sigaction.cc b/test/syscalls/linux/sigaction.cc deleted file mode 100644 index 9d9dd57a8..000000000 --- a/test/syscalls/linux/sigaction.cc +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/syscall.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SigactionTest, GetLessThanOrEqualToZeroFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(-1, nullptr, &act), SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(sigaction(0, nullptr, &act), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, SetLessThanOrEqualToZeroFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(0, &act, nullptr), SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT(sigaction(0, &act, nullptr), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, GetGreaterThanMaxFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(SIGRTMAX + 1, nullptr, &act), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, SetGreaterThanMaxFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(SIGRTMAX + 1, &act, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, SetSigkillFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(SIGKILL, nullptr, &act), SyscallSucceeds()); - ASSERT_THAT(sigaction(SIGKILL, &act, nullptr), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, SetSigstopFails) { - struct sigaction act = {}; - ASSERT_THAT(sigaction(SIGSTOP, nullptr, &act), SyscallSucceeds()); - ASSERT_THAT(sigaction(SIGSTOP, &act, nullptr), SyscallFailsWithErrno(EINVAL)); -} - -TEST(SigactionTest, BadSigsetFails) { - constexpr size_t kWrongSigSetSize = 43; - - struct sigaction act = {}; - - // The syscall itself (rather than the libc wrapper) takes the sigset_t size. - ASSERT_THAT( - syscall(SYS_rt_sigaction, SIGTERM, nullptr, &act, kWrongSigSetSize), - SyscallFailsWithErrno(EINVAL)); - ASSERT_THAT( - syscall(SYS_rt_sigaction, SIGTERM, &act, nullptr, kWrongSigSetSize), - SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sigaltstack.cc b/test/syscalls/linux/sigaltstack.cc deleted file mode 100644 index 24e7c4960..000000000 --- a/test/syscalls/linux/sigaltstack.cc +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <signal.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> - -#include <functional> -#include <vector> - -#include "gtest/gtest.h" -#include "test/util/cleanup.h" -#include "test/util/fs_util.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<Cleanup> ScopedSigaltstack(stack_t const& stack) { - stack_t old_stack; - int rc = sigaltstack(&stack, &old_stack); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "sigaltstack failed"); - } - return Cleanup([old_stack] { - EXPECT_THAT(sigaltstack(&old_stack, nullptr), SyscallSucceeds()); - }); -} - -volatile bool got_signal = false; -volatile int sigaltstack_errno = 0; -volatile int ss_flags = 0; - -void sigaltstack_handler(int sig, siginfo_t* siginfo, void* arg) { - got_signal = true; - - stack_t stack; - int ret = sigaltstack(nullptr, &stack); - MaybeSave(); - if (ret < 0) { - sigaltstack_errno = errno; - return; - } - ss_flags = stack.ss_flags; -} - -TEST(SigaltstackTest, Success) { - std::vector<char> stack_mem(SIGSTKSZ); - stack_t stack = {}; - stack.ss_sp = stack_mem.data(); - stack.ss_size = stack_mem.size(); - auto const cleanup_sigstack = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack)); - - struct sigaction sa = {}; - sa.sa_sigaction = sigaltstack_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO | SA_ONSTACK; - auto const cleanup_sa = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - // Send signal to this thread, as sigaltstack is per-thread. - EXPECT_THAT(tgkill(getpid(), gettid(), SIGUSR1), SyscallSucceeds()); - - EXPECT_TRUE(got_signal); - EXPECT_EQ(sigaltstack_errno, 0); - EXPECT_NE(0, ss_flags & SS_ONSTACK); -} - -TEST(SigaltstackTest, ResetByExecve) { - std::vector<char> stack_mem(SIGSTKSZ); - stack_t stack = {}; - stack.ss_sp = stack_mem.data(); - stack.ss_size = stack_mem.size(); - auto const cleanup_sigstack = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack)); - - std::string full_path = RunfilePath("test/syscalls/linux/sigaltstack_check"); - - pid_t child_pid = -1; - int execve_errno = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(full_path, {"sigaltstack_check"}, {}, nullptr, &child_pid, - &execve_errno)); - - ASSERT_GT(child_pid, 0); - ASSERT_EQ(execve_errno, 0); - - int status = 0; - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), 0); -} - -volatile bool badhandler_on_sigaltstack = true; // Set by the handler. -char* volatile badhandler_low_water_mark = nullptr; // Set by the handler. -volatile uint8_t badhandler_recursive_faults = 0; // Consumed by the handler. - -void badhandler(int sig, siginfo_t* siginfo, void* arg) { - char stack_var = 0; - char* current_ss = &stack_var; - - stack_t stack; - int ret = sigaltstack(nullptr, &stack); - if (ret < 0 || (stack.ss_flags & SS_ONSTACK) != SS_ONSTACK) { - // We should always be marked as being on the stack. Don't allow this to hit - // the bottom if this is ever not true (the main test will fail as a - // result, but we still need to unwind the recursive faults). - badhandler_on_sigaltstack = false; - } - if (current_ss < badhandler_low_water_mark) { - // Record the low point for the signal stack. We never expected this to be - // before stack bottom, but this is asserted in the actual test. - badhandler_low_water_mark = current_ss; - } - if (badhandler_recursive_faults > 0) { - badhandler_recursive_faults--; - Fault(); - } - FixupFault(reinterpret_cast<ucontext_t*>(arg)); -} - -TEST(SigaltstackTest, WalksOffBottom) { - // This test marks the upper half of the stack_mem array as the signal stack. - // It asserts that when a fault occurs in the handler (already on the signal - // stack), we eventually continue to fault our way off the stack. We should - // not revert to the top of the signal stack when we fall off the bottom and - // the signal stack should remain "in use". When we fall off the signal stack, - // we should have an unconditional signal delivered and not start using the - // first part of the stack_mem array. - std::vector<char> stack_mem(SIGSTKSZ * 2); - stack_t stack = {}; - stack.ss_sp = stack_mem.data() + SIGSTKSZ; // See above: upper half. - stack.ss_size = SIGSTKSZ; // Only one half the array. - auto const cleanup_sigstack = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack)); - - // Setup the handler: this must be for SIGSEGV, and it must allow proper - // nesting (no signal mask, no defer) so that we can trigger multiple times. - // - // When we walk off the bottom of the signal stack and force signal delivery - // of a SIGSEGV, the handler will revert to the default behavior (kill). - struct sigaction sa = {}; - sa.sa_sigaction = badhandler; - sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER; - auto const cleanup_sa = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); - - // Trigger a single fault. - badhandler_low_water_mark = - static_cast<char*>(stack.ss_sp) + SIGSTKSZ; // Expected top. - badhandler_recursive_faults = 0; // Disable refault. - Fault(); - EXPECT_TRUE(badhandler_on_sigaltstack); - EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds()); - EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0); - EXPECT_LT(badhandler_low_water_mark, - reinterpret_cast<char*>(stack.ss_sp) + 2 * SIGSTKSZ); - EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp)); - - // Trigger two faults. - char* prev_low_water_mark = badhandler_low_water_mark; // Previous top. - badhandler_recursive_faults = 1; // One refault. - Fault(); - ASSERT_TRUE(badhandler_on_sigaltstack); - EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds()); - EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0); - EXPECT_LT(badhandler_low_water_mark, prev_low_water_mark); - EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp)); - - // Calculate the stack growth for a fault, and set the recursive faults to - // ensure that the signal handler stack required exceeds our marked stack area - // by a minimal amount. It should remain in the valid stack_mem area so that - // we can test the signal is forced merely by going out of the signal stack - // bounds, not by a genuine fault. - uintptr_t frame_size = - static_cast<uintptr_t>(prev_low_water_mark - badhandler_low_water_mark); - badhandler_recursive_faults = (SIGSTKSZ + frame_size) / frame_size; - EXPECT_EXIT(Fault(), ::testing::KilledBySignal(SIGSEGV), ""); -} - -volatile int setonstack_retval = 0; // Set by the handler. -volatile int setonstack_errno = 0; // Set by the handler. - -void setonstack(int sig, siginfo_t* siginfo, void* arg) { - char stack_mem[SIGSTKSZ]; - stack_t stack = {}; - stack.ss_sp = &stack_mem[0]; - stack.ss_size = SIGSTKSZ; - setonstack_retval = sigaltstack(&stack, nullptr); - setonstack_errno = errno; - FixupFault(reinterpret_cast<ucontext_t*>(arg)); -} - -TEST(SigaltstackTest, SetWhileOnStack) { - // Reserve twice as much stack here, since the handler will allocate a vector - // of size SIGTKSZ and attempt to set the sigaltstack to that value. - std::vector<char> stack_mem(2 * SIGSTKSZ); - stack_t stack = {}; - stack.ss_sp = stack_mem.data(); - stack.ss_size = stack_mem.size(); - auto const cleanup_sigstack = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack)); - - // See above. - struct sigaction sa = {}; - sa.sa_sigaction = setonstack; - sa.sa_flags = SA_SIGINFO | SA_ONSTACK; - auto const cleanup_sa = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); - - // Trigger a fault. - Fault(); - - // The set should have failed. - EXPECT_EQ(setonstack_retval, -1); - EXPECT_EQ(setonstack_errno, EPERM); -} - -TEST(SigaltstackTest, SetCurrentStack) { - // This is executed as an exit test because once the signal stack is set to - // the local stack, there's no good way to unwind. We don't want to taint the - // test of any other tests that might run within this process. - EXPECT_EXIT( - { - char stack_value = 0; - stack_t stack = {}; - stack.ss_sp = &stack_value - kPageSize; // Lower than current level. - stack.ss_size = 2 * kPageSize; // => &stack_value +/- kPageSize. - TEST_CHECK(sigaltstack(&stack, nullptr) == 0); - TEST_CHECK(sigaltstack(nullptr, &stack) == 0); - TEST_CHECK((stack.ss_flags & SS_ONSTACK) != 0); - - // Should not be able to change the stack (even no-op). - TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM); - - // Should not be able to disable the stack. - stack.ss_flags = SS_DISABLE; - TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM); - exit(0); - }, - ::testing::ExitedWithCode(0), ""); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sigaltstack_check.cc b/test/syscalls/linux/sigaltstack_check.cc deleted file mode 100644 index 5ac1b661d..000000000 --- a/test/syscalls/linux/sigaltstack_check.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Checks that there is no alternate signal stack by default. -// -// Used by a test in sigaltstack.cc. -#include <errno.h> -#include <signal.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> - -#include "test/util/logging.h" - -int main(int /* argc */, char** /* argv */) { - stack_t stack; - TEST_CHECK(sigaltstack(nullptr, &stack) >= 0); - TEST_CHECK(stack.ss_flags == SS_DISABLE); - TEST_CHECK(stack.ss_sp == 0); - TEST_CHECK(stack.ss_size == 0); - return 0; -} diff --git a/test/syscalls/linux/signalfd.cc b/test/syscalls/linux/signalfd.cc deleted file mode 100644 index c86cd2755..000000000 --- a/test/syscalls/linux/signalfd.cc +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <poll.h> -#include <signal.h> -#include <stdio.h> -#include <string.h> -#include <sys/signalfd.h> -#include <unistd.h> - -#include <functional> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/synchronization/mutex.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -using ::testing::KilledBySignal; - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int kSigno = SIGUSR1; -constexpr int kSignoMax = 64; // SIGRTMAX -constexpr int kSignoAlt = SIGUSR2; - -// Returns a new signalfd. -inline PosixErrorOr<FileDescriptor> NewSignalFD(sigset_t* mask, int flags = 0) { - int fd = signalfd(-1, mask, flags); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, "signalfd"); - } - return FileDescriptor(fd); -} - -class SignalfdTest : public ::testing::TestWithParam<int> {}; - -TEST_P(SignalfdTest, Basic) { - int signo = GetParam(); - // Create the signalfd. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, signo); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, 0)); - - // Deliver the blocked signal. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, signo)); - ASSERT_THAT(tgkill(getpid(), gettid(), signo), SyscallSucceeds()); - - // We should now read the signal. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); -} - -TEST_P(SignalfdTest, MaskWorks) { - int signo = GetParam(); - // Create two signalfds with different masks. - sigset_t mask1, mask2; - sigemptyset(&mask1); - sigemptyset(&mask2); - sigaddset(&mask1, signo); - sigaddset(&mask2, kSignoAlt); - FileDescriptor fd1 = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask1, 0)); - FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask2, 0)); - - // Deliver the two signals. - const auto scoped_sigmask1 = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, signo)); - const auto scoped_sigmask2 = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, kSignoAlt)); - ASSERT_THAT(tgkill(getpid(), gettid(), signo), SyscallSucceeds()); - ASSERT_THAT(tgkill(getpid(), gettid(), kSignoAlt), SyscallSucceeds()); - - // We should see the signals on the appropriate signalfds. - // - // We read in the opposite order as the signals deliver above, to ensure that - // we don't happen to read the correct signal from the correct signalfd. - struct signalfd_siginfo rbuf1, rbuf2; - ASSERT_THAT(read(fd2.get(), &rbuf2, sizeof(rbuf2)), - SyscallSucceedsWithValue(sizeof(rbuf2))); - EXPECT_EQ(rbuf2.ssi_signo, kSignoAlt); - ASSERT_THAT(read(fd1.get(), &rbuf1, sizeof(rbuf1)), - SyscallSucceedsWithValue(sizeof(rbuf1))); - EXPECT_EQ(rbuf1.ssi_signo, signo); -} - -TEST(Signalfd, Cloexec) { - // Exec tests confirm that O_CLOEXEC has the intended effect. We just create a - // signalfd with the appropriate flag here and assert that the FD has it set. - sigset_t mask; - sigemptyset(&mask); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, SFD_CLOEXEC)); - EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST_P(SignalfdTest, Blocking) { - int signo = GetParam(); - // Create the signalfd in blocking mode. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, signo); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, 0)); - - // Shared tid variable. - absl::Mutex mu; - bool has_tid = false; - pid_t tid; - - // Start a thread reading. - ScopedThread t([&] { - // Copy the tid and notify the caller. - { - absl::MutexLock ml(&mu); - tid = gettid(); - has_tid = true; - } - - // Read the signal from the signalfd. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); - }); - - // Wait until blocked. - absl::MutexLock ml(&mu); - mu.Await(absl::Condition(&has_tid)); - - // Deliver the signal to either the waiting thread, or - // to this thread. N.B. this is a bug in the core gVisor - // behavior for signalfd, and needs to be fixed. - // - // See gvisor.dev/issue/139. - if (IsRunningOnGvisor()) { - ASSERT_THAT(tgkill(getpid(), gettid(), signo), SyscallSucceeds()); - } else { - ASSERT_THAT(tgkill(getpid(), tid, signo), SyscallSucceeds()); - } - - // Ensure that it was received. - t.Join(); -} - -TEST_P(SignalfdTest, ThreadGroup) { - int signo = GetParam(); - // Create the signalfd in blocking mode. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, signo); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, 0)); - - // Shared variable. - absl::Mutex mu; - bool first = false; - bool second = false; - - // Start a thread reading. - ScopedThread t([&] { - // Read the signal from the signalfd. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); - - // Wait for the other thread. - absl::MutexLock ml(&mu); - first = true; - mu.Await(absl::Condition(&second)); - }); - - // Deliver the signal to the threadgroup. - ASSERT_THAT(kill(getpid(), signo), SyscallSucceeds()); - - // Wait for the first thread to process. - { - absl::MutexLock ml(&mu); - mu.Await(absl::Condition(&first)); - } - - // Deliver to the thread group again (other thread still exists). - ASSERT_THAT(kill(getpid(), signo), SyscallSucceeds()); - - // Ensure that we can also receive it. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); - - // Mark the test as done. - { - absl::MutexLock ml(&mu); - second = true; - } - - // The other thread should be joinable. - t.Join(); -} - -TEST_P(SignalfdTest, Nonblock) { - int signo = GetParam(); - // Create the signalfd in non-blocking mode. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, signo); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, SFD_NONBLOCK)); - - // We should return if we attempt to read. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Block and deliver the signal. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, signo)); - ASSERT_THAT(tgkill(getpid(), gettid(), signo), SyscallSucceeds()); - - // Ensure that a read actually works. - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); - - // Should block again. - EXPECT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST_P(SignalfdTest, SetMask) { - int signo = GetParam(); - // Create the signalfd matching nothing. - sigset_t mask; - sigemptyset(&mask); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, SFD_NONBLOCK)); - - // Block and deliver a signal. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, signo)); - ASSERT_THAT(tgkill(getpid(), gettid(), signo), SyscallSucceeds()); - - // We should have nothing. - struct signalfd_siginfo rbuf; - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Change the signal mask. - sigaddset(&mask, signo); - ASSERT_THAT(signalfd(fd.get(), &mask, 0), SyscallSucceeds()); - - // We should now have the signal. - ASSERT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf.ssi_signo, signo); -} - -TEST_P(SignalfdTest, Poll) { - int signo = GetParam(); - // Create the signalfd. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, signo); - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, 0)); - - // Block the signal, and start a thread to deliver it. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, signo)); - pid_t orig_tid = gettid(); - ScopedThread t([&] { - absl::SleepFor(absl::Seconds(5)); - ASSERT_THAT(tgkill(getpid(), orig_tid, signo), SyscallSucceeds()); - }); - - // Start polling for the signal. We expect that it is not available at the - // outset, but then becomes available when the signal is sent. We give a - // timeout of 10000ms (or the delay above + 5 seconds of additional grace - // time). - struct pollfd poll_fd = {fd.get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); - - // Actually read the signal to prevent delivery. - struct signalfd_siginfo rbuf; - EXPECT_THAT(read(fd.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); -} - -std::string PrintSigno(::testing::TestParamInfo<int> info) { - switch (info.param) { - case kSigno: - return "kSigno"; - case kSignoMax: - return "kSignoMax"; - default: - return absl::StrCat(info.param); - } -} -INSTANTIATE_TEST_SUITE_P(Signalfd, SignalfdTest, - ::testing::Values(kSigno, kSignoMax), PrintSigno); - -TEST(Signalfd, Ppoll) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGKILL); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, SFD_CLOEXEC)); - - // Ensure that the given ppoll blocks. - struct pollfd pfd = {}; - pfd.fd = fd.get(); - pfd.events = POLLIN; - struct timespec timeout = {}; - timeout.tv_sec = 1; - EXPECT_THAT(RetryEINTR(ppoll)(&pfd, 1, &timeout, &mask), - SyscallSucceedsWithValue(0)); -} - -TEST(Signalfd, KillStillKills) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGKILL); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NewSignalFD(&mask, SFD_CLOEXEC)); - - // Just because there is a signalfd, we shouldn't see any change in behavior - // for unblockable signals. It's easier to test this with SIGKILL. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, SIGKILL)); - EXPECT_EXIT(tgkill(getpid(), gettid(), SIGKILL), KilledBySignal(SIGKILL), ""); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // These tests depend on delivering signals. Block them up front so that all - // other threads created by TestInit will also have them blocked, and they - // will not interface with the rest of the test. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, gvisor::testing::kSigno); - sigaddset(&set, gvisor::testing::kSignoMax); - sigaddset(&set, gvisor::testing::kSignoAlt); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - - gvisor::testing::TestInit(&argc, &argv); - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/sigprocmask.cc b/test/syscalls/linux/sigprocmask.cc deleted file mode 100644 index a603fc1d1..000000000 --- a/test/syscalls/linux/sigprocmask.cc +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <stddef.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Signals numbers used for testing. -static constexpr int kTestSignal1 = SIGUSR1; -static constexpr int kTestSignal2 = SIGUSR2; - -static int raw_sigprocmask(int how, const sigset_t* set, sigset_t* oldset) { - return syscall(SYS_rt_sigprocmask, how, set, oldset, _NSIG / 8); -} - -// count of the number of signals received -int signal_count[kMaxSignal + 1]; - -// signal handler increments the signal counter -void SigHandler(int sig, siginfo_t* info, void* context) { - TEST_CHECK(sig > 0 && sig <= kMaxSignal); - signal_count[sig] += 1; -} - -// The test fixture saves and restores the signal mask and -// sets up handlers for kTestSignal1 and kTestSignal2. -class SigProcMaskTest : public ::testing::Test { - protected: - void SetUp() override { - // Save the current signal mask. - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &mask_), - SyscallSucceeds()); - - // Setup signal handlers for kTestSignal1 and kTestSignal2. - struct sigaction sa; - sa.sa_sigaction = SigHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - EXPECT_THAT(sigaction(kTestSignal1, &sa, &sa_test_sig_1_), - SyscallSucceeds()); - EXPECT_THAT(sigaction(kTestSignal2, &sa, &sa_test_sig_2_), - SyscallSucceeds()); - - // Clear the signal counters. - memset(signal_count, 0, sizeof(signal_count)); - } - - void TearDown() override { - // Restore the signal mask. - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &mask_, nullptr), - SyscallSucceeds()); - - // Restore the signal handlers for kTestSignal1 and kTestSignal2. - EXPECT_THAT(sigaction(kTestSignal1, &sa_test_sig_1_, nullptr), - SyscallSucceeds()); - EXPECT_THAT(sigaction(kTestSignal2, &sa_test_sig_2_, nullptr), - SyscallSucceeds()); - } - - private: - sigset_t mask_; - struct sigaction sa_test_sig_1_; - struct sigaction sa_test_sig_2_; -}; - -// Both sigsets nullptr should succeed and do nothing. -TEST_F(SigProcMaskTest, NullAddress) { - EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, nullptr, NULL), SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, nullptr, NULL), SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, NULL), SyscallSucceeds()); -} - -// Bad address for either sigset should fail with EFAULT. -TEST_F(SigProcMaskTest, BadAddress) { - sigset_t* bad_addr = reinterpret_cast<sigset_t*>(-1); - - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, bad_addr, nullptr), - SyscallFailsWithErrno(EFAULT)); - - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, bad_addr), - SyscallFailsWithErrno(EFAULT)); -} - -// Bad value of the "how" parameter should fail with EINVAL. -TEST_F(SigProcMaskTest, BadParameter) { - int bad_param_1 = -1; - int bad_param_2 = 42; - - sigset_t set1; - sigemptyset(&set1); - - EXPECT_THAT(raw_sigprocmask(bad_param_1, &set1, nullptr), - SyscallFailsWithErrno(EINVAL)); - - EXPECT_THAT(raw_sigprocmask(bad_param_2, &set1, nullptr), - SyscallFailsWithErrno(EINVAL)); -} - -// Check that we can get the current signal mask. -TEST_F(SigProcMaskTest, GetMask) { - sigset_t set1; - sigset_t set2; - - sigemptyset(&set1); - sigfillset(&set2); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &set1), SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &set2), SyscallSucceeds()); - EXPECT_THAT(set1, EqualsSigset(set2)); -} - -// Check that we can set the signal mask. -TEST_F(SigProcMaskTest, SetMask) { - sigset_t actual; - sigset_t expected; - - // Try to mask all signals - sigfillset(&expected); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr), - SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - // sigprocmask() should have silently ignored SIGKILL and SIGSTOP. - sigdelset(&expected, SIGSTOP); - sigdelset(&expected, SIGKILL); - EXPECT_THAT(actual, EqualsSigset(expected)); - - // Try to clear the signal mask - sigemptyset(&expected); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr), - SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - EXPECT_THAT(actual, EqualsSigset(expected)); - - // Try to set a mask with one signal. - sigemptyset(&expected); - sigaddset(&expected, kTestSignal1); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr), - SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - EXPECT_THAT(actual, EqualsSigset(expected)); -} - -// Check that we can add and remove signals. -TEST_F(SigProcMaskTest, BlockUnblock) { - sigset_t actual; - sigset_t expected; - - // Try to set a mask with one signal. - sigemptyset(&expected); - sigaddset(&expected, kTestSignal1); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &expected, nullptr), - SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - EXPECT_THAT(actual, EqualsSigset(expected)); - - // Try to add another signal. - sigset_t block; - sigemptyset(&block); - sigaddset(&block, kTestSignal2); - EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, &block, nullptr), SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - sigaddset(&expected, kTestSignal2); - EXPECT_THAT(actual, EqualsSigset(expected)); - - // Try to remove a signal. - sigset_t unblock; - sigemptyset(&unblock); - sigaddset(&unblock, kTestSignal1); - EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, &unblock, nullptr), - SyscallSucceeds()); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, nullptr, &actual), - SyscallSucceeds()); - sigdelset(&expected, kTestSignal1); - EXPECT_THAT(actual, EqualsSigset(expected)); -} - -// Test that the signal mask actually blocks signals. -TEST_F(SigProcMaskTest, SignalHandler) { - sigset_t mask; - - // clear the signal mask - sigemptyset(&mask); - EXPECT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, nullptr), SyscallSucceeds()); - - // Check the initial signal counts. - EXPECT_EQ(0, signal_count[kTestSignal1]); - EXPECT_EQ(0, signal_count[kTestSignal2]); - - // Check that both kTestSignal1 and kTestSignal2 are not blocked. - raise(kTestSignal1); - raise(kTestSignal2); - EXPECT_EQ(1, signal_count[kTestSignal1]); - EXPECT_EQ(1, signal_count[kTestSignal2]); - - // Block kTestSignal1. - sigaddset(&mask, kTestSignal1); - EXPECT_THAT(raw_sigprocmask(SIG_BLOCK, &mask, nullptr), SyscallSucceeds()); - - // Check that kTestSignal1 is blocked. - raise(kTestSignal1); - raise(kTestSignal2); - EXPECT_EQ(1, signal_count[kTestSignal1]); - EXPECT_EQ(2, signal_count[kTestSignal2]); - - // Unblock kTestSignal1. - sigaddset(&mask, kTestSignal1); - EXPECT_THAT(raw_sigprocmask(SIG_UNBLOCK, &mask, nullptr), SyscallSucceeds()); - - // Check that the unblocked kTestSignal1 has been delivered. - EXPECT_EQ(2, signal_count[kTestSignal1]); - EXPECT_EQ(2, signal_count[kTestSignal2]); -} - -// Check that sigprocmask correctly handles aliasing of the set and oldset -// pointers. Regression test for b/30502311. -TEST_F(SigProcMaskTest, AliasedSets) { - sigset_t mask; - - // Set a mask in which only kTestSignal1 is blocked. - sigset_t mask1; - sigemptyset(&mask1); - sigaddset(&mask1, kTestSignal1); - mask = mask1; - ASSERT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, nullptr), SyscallSucceeds()); - - // Exchange it with a mask in which only kTestSignal2 is blocked. - sigset_t mask2; - sigemptyset(&mask2); - sigaddset(&mask2, kTestSignal2); - mask = mask2; - ASSERT_THAT(raw_sigprocmask(SIG_SETMASK, &mask, &mask), SyscallSucceeds()); - - // Check that the exchange succeeeded: - // mask should now contain the previously-set mask blocking only kTestSignal1. - EXPECT_THAT(mask, EqualsSigset(mask1)); - // The current mask should block only kTestSignal2. - ASSERT_THAT(raw_sigprocmask(0, nullptr, &mask), SyscallSucceeds()); - EXPECT_THAT(mask, EqualsSigset(mask2)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sigreturn_amd64.cc b/test/syscalls/linux/sigreturn_amd64.cc deleted file mode 100644 index 6227774a4..000000000 --- a/test/syscalls/linux/sigreturn_amd64.cc +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/types.h> -#include <sys/ucontext.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr uint64_t kOrigRcx = 0xdeadbeeffacefeed; -constexpr uint64_t kOrigR11 = 0xfacefeedbaad1dea; - -volatile int gotvtalrm, ready; - -void sigvtalrm(int sig, siginfo_t* siginfo, void* _uc) { - ucontext_t* uc = reinterpret_cast<ucontext_t*>(_uc); - - // Verify that: - // - test is in the busy-wait loop waiting for signal. - // - %rcx and %r11 values in mcontext_t match kOrigRcx and kOrigR11. - if (ready && - static_cast<uint64_t>(uc->uc_mcontext.gregs[REG_RCX]) == kOrigRcx && - static_cast<uint64_t>(uc->uc_mcontext.gregs[REG_R11]) == kOrigR11) { - // Modify the values %rcx and %r11 in the ucontext. These are the - // values seen by the application after the signal handler returns. - uc->uc_mcontext.gregs[REG_RCX] = ~kOrigRcx; - uc->uc_mcontext.gregs[REG_R11] = ~kOrigR11; - gotvtalrm = 1; - } -} - -TEST(SigIretTest, CheckRcxR11) { - // Setup signal handler for SIGVTALRM. - struct sigaction sa = {}; - sigfillset(&sa.sa_mask); - sa.sa_sigaction = sigvtalrm; - sa.sa_flags = SA_SIGINFO; - auto const action_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGVTALRM, sa)); - - auto const mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGVTALRM)); - - // Setup itimer to fire after 500 msecs. - struct itimerval itimer = {}; - itimer.it_value.tv_usec = 500 * 1000; // 500 msecs. - auto const timer_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_VIRTUAL, itimer)); - - // Initialize %rcx and %r11 and spin until the signal handler returns. - uint64_t rcx = kOrigRcx; - uint64_t r11 = kOrigR11; - asm volatile( - "movq %[rcx], %%rcx;" // %rcx = rcx - "movq %[r11], %%r11;" // %r11 = r11 - "movl $1, %[ready];" // ready = 1 - "1: pause; cmpl $0, %[gotvtalrm]; je 1b;" // while (!gotvtalrm); - "movq %%rcx, %[rcx];" // rcx = %rcx - "movq %%r11, %[r11];" // r11 = %r11 - : [ ready ] "=m"(ready), [ rcx ] "+m"(rcx), [ r11 ] "+m"(r11) - : [ gotvtalrm ] "m"(gotvtalrm) - : "cc", "memory", "rcx", "r11"); - - // If sigreturn(2) returns via 'sysret' then %rcx and %r11 will be - // clobbered and set to 'ptregs->rip' and 'ptregs->rflags' respectively. - // - // The following check verifies that %rcx and %r11 were not clobbered - // when returning from the signal handler (via sigreturn(2)). - EXPECT_EQ(rcx, ~kOrigRcx); - EXPECT_EQ(r11, ~kOrigR11); -} - -constexpr uint64_t kNonCanonicalRip = 0xCCCC000000000000; - -// Test that a non-canonical signal handler faults as expected. -TEST(SigIretTest, BadHandler) { - struct sigaction sa = {}; - sa.sa_sigaction = - reinterpret_cast<void (*)(int, siginfo_t*, void*)>(kNonCanonicalRip); - auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - pid_t pid = fork(); - if (pid == 0) { - // Child, wait for signal. - while (1) { - pause(); - } - } - ASSERT_THAT(pid, SyscallSucceeds()); - - EXPECT_THAT(kill(pid, SIGUSR1), SyscallSucceeds()); - - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) - << "status = " << status; -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // SigIretTest.CheckRcxR11 depends on delivering SIGVTALRM to the main thread. - // Block SIGVTALRM so that any other threads created by TestInit will also - // have SIGVTALRM blocked. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGVTALRM); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/sigreturn_arm64.cc b/test/syscalls/linux/sigreturn_arm64.cc deleted file mode 100644 index 2c19e2984..000000000 --- a/test/syscalls/linux/sigreturn_arm64.cc +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/unistd.h> -#include <signal.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/ucontext.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr uint64_t kOrigX7 = 0xdeadbeeffacefeed; - -void sigvtalrm(int sig, siginfo_t* siginfo, void* _uc) { - ucontext_t* uc = reinterpret_cast<ucontext_t*>(_uc); - - // Verify that: - // - x7 value in mcontext_t matches kOrigX7. - if (uc->uc_mcontext.regs[7] == kOrigX7) { - // Modify the value x7 in the ucontext. This is the value seen by the - // application after the signal handler returns. - uc->uc_mcontext.regs[7] = ~kOrigX7; - } -} - -int testX7(uint64_t* val, uint64_t sysno, uint64_t tgid, uint64_t tid, - uint64_t signo) { - register uint64_t* x9 __asm__("x9") = val; - register uint64_t x8 __asm__("x8") = sysno; - register uint64_t x0 __asm__("x0") = tgid; - register uint64_t x1 __asm__("x1") = tid; - register uint64_t x2 __asm__("x2") = signo; - - // Initialize x7, send SIGVTALRM to itself and read x7. - __asm__( - "ldr x7, [x9, 0]\n" - "svc 0\n" - "str x7, [x9, 0]\n" - : "=r"(x0) - : "r"(x0), "r"(x1), "r"(x2), "r"(x9), "r"(x8) - : "x7"); - return x0; -} - -// On ARM64, when ptrace stops on a system call, it uses the x7 register to -// indicate whether the stop has been signalled from syscall entry or syscall -// exit. This means that we can't get a value of this register and we can't -// change it. More details are in the comment for tracehook_report_syscall in -// arch/arm64/kernel/ptrace.c. -// -// CheckR7 checks that the ptrace platform handles the x7 register properly. -TEST(SigreturnTest, CheckX7) { - // Setup signal handler for SIGVTALRM. - struct sigaction sa = {}; - sigfillset(&sa.sa_mask); - sa.sa_sigaction = sigvtalrm; - sa.sa_flags = SA_SIGINFO; - auto const action_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGVTALRM, sa)); - - auto const mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGVTALRM)); - - uint64_t x7 = kOrigX7; - - testX7(&x7, __NR_tgkill, getpid(), syscall(__NR_gettid), SIGVTALRM); - - // The following check verifies that %x7 was not clobbered - // when returning from the signal handler (via sigreturn(2)). - EXPECT_EQ(x7, ~kOrigX7); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sigstop.cc b/test/syscalls/linux/sigstop.cc deleted file mode 100644 index b2fcedd62..000000000 --- a/test/syscalls/linux/sigstop.cc +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <stdlib.h> -#include <sys/select.h> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(bool, sigstop_test_child, false, - "If true, run the SigstopTest child workload."); - -namespace gvisor { -namespace testing { - -namespace { - -constexpr absl::Duration kChildStartupDelay = absl::Seconds(5); -constexpr absl::Duration kChildMainThreadDelay = absl::Seconds(10); -constexpr absl::Duration kChildExtraThreadDelay = absl::Seconds(15); -constexpr absl::Duration kPostSIGSTOPDelay = absl::Seconds(20); - -// Comparisons on absl::Duration aren't yet constexpr (2017-07-14), so we -// can't just use static_assert. -TEST(SigstopTest, TimesAreRelativelyConsistent) { - EXPECT_LT(kChildStartupDelay, kChildMainThreadDelay) - << "Child process will exit before the parent process attempts to stop " - "it"; - EXPECT_LT(kChildMainThreadDelay, kChildExtraThreadDelay) - << "Secondary thread in child process will exit before main thread, " - "causing it to exit with the wrong code"; - EXPECT_LT(kChildExtraThreadDelay, kPostSIGSTOPDelay) - << "Parent process stops waiting before child process may exit if " - "improperly stopped, rendering the test ineffective"; -} - -// Exit codes communicated from the child workload to the parent test process. -constexpr int kChildMainThreadExitCode = 10; -constexpr int kChildExtraThreadExitCode = 11; - -TEST(SigstopTest, Correctness) { - pid_t child_pid = -1; - int execve_errno = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--sigstop_test_child"}, - {}, nullptr, &child_pid, &execve_errno)); - - ASSERT_GT(child_pid, 0); - ASSERT_EQ(execve_errno, 0); - - // Wait for the child subprocess to start the second thread before stopping - // it. - absl::SleepFor(kChildStartupDelay); - ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds()); - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, WUNTRACED), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFSTOPPED(status)); - EXPECT_EQ(SIGSTOP, WSTOPSIG(status)); - - // Sleep for longer than either of the sleeps in the child subprocess, - // expecting the child to stay alive because it's stopped. - absl::SleepFor(kPostSIGSTOPDelay); - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, WNOHANG), - SyscallSucceedsWithValue(0)); - - // Resume the child. - ASSERT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds()); - - EXPECT_THAT(RetryEINTR(waitpid)(child_pid, &status, WCONTINUED), - SyscallSucceedsWithValue(child_pid)); - EXPECT_TRUE(WIFCONTINUED(status)); - - // Expect it to die. - ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), kChildMainThreadExitCode); -} - -// Like base:SleepFor, but tries to avoid counting time spent stopped due to a -// stop signal toward the sleep. -// -// This is required due to an inconsistency in how nanosleep(2) and stop signals -// interact on Linux. When nanosleep is interrupted, it writes the remaining -// time back to its second timespec argument, so that if nanosleep is -// interrupted by a signal handler then userspace can immediately call nanosleep -// again with that timespec. However, if nanosleep is automatically restarted -// (because it's interrupted by a signal that is not delivered to a handler, -// such as a stop signal), it's restarted based on the timer's former *absolute* -// expiration time (via ERESTART_RESTARTBLOCK => SYS_restart_syscall => -// hrtimer_nanosleep_restart). This means that time spent stopped is effectively -// counted as time spent sleeping, resulting in less time spent sleeping than -// expected. -// -// Dividing the sleep into multiple smaller sleeps limits the impact of this -// effect to the length of each sleep during which a stop occurs; for example, -// if a sleeping process is only stopped once, SleepIgnoreStopped can -// under-sleep by at most 100ms. -void SleepIgnoreStopped(absl::Duration d) { - absl::Duration const max_sleep = absl::Milliseconds(100); - while (d > absl::ZeroDuration()) { - absl::Duration to_sleep = std::min(d, max_sleep); - absl::SleepFor(to_sleep); - d -= to_sleep; - } -} - -void RunChild() { - // Start another thread that attempts to call exit_group with a different - // error code, in order to verify that SIGSTOP stops this thread as well. - ScopedThread t([] { - SleepIgnoreStopped(kChildExtraThreadDelay); - exit(kChildExtraThreadExitCode); - }); - SleepIgnoreStopped(kChildMainThreadDelay); - exit(kChildMainThreadExitCode); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_sigstop_test_child)) { - gvisor::testing::RunChild(); - return 1; - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/sigtimedwait.cc b/test/syscalls/linux/sigtimedwait.cc deleted file mode 100644 index 4f8afff15..000000000 --- a/test/syscalls/linux/sigtimedwait.cc +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/wait.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// N.B. main() blocks SIGALRM and SIGCHLD on all threads. - -constexpr int kAlarmSecs = 12; - -void NoopHandler(int sig, siginfo_t* info, void* context) {} - -TEST(SigtimedwaitTest, InvalidTimeout) { - sigset_t mask; - sigemptyset(&mask); - struct timespec timeout = {0, 1000000001}; - EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout), - SyscallFailsWithErrno(EINVAL)); - timeout = {-1, 0}; - EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout), - SyscallFailsWithErrno(EINVAL)); - timeout = {0, -1}; - EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout), - SyscallFailsWithErrno(EINVAL)); -} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and wait. -TEST(SigtimedwaitTest, AlarmReturnsAlarm_NoRandomSave) { - struct itimerval itv = {}; - itv.it_value.tv_sec = kAlarmSecs; - const auto itimer_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv)); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGALRM); - siginfo_t info = {}; - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, &info, nullptr), - SyscallSucceedsWithValue(SIGALRM)); - EXPECT_EQ(SIGALRM, info.si_signo); -} - -// No random save as the test relies on alarm timing. Cooperative save tests -// already cover the save between alarm and wait. -TEST(SigtimedwaitTest, NullTimeoutReturnsEINTR_NoRandomSave) { - struct sigaction sa; - sa.sa_sigaction = NoopHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - const auto action_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGALRM, sa)); - - const auto mask_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGALRM)); - - struct itimerval itv = {}; - itv.it_value.tv_sec = kAlarmSecs; - const auto itimer_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedItimer(ITIMER_REAL, itv)); - - sigset_t mask; - sigemptyset(&mask); - EXPECT_THAT(sigtimedwait(&mask, nullptr, nullptr), - SyscallFailsWithErrno(EINTR)); -} - -TEST(SigtimedwaitTest, LegitTimeoutReturnsEAGAIN) { - sigset_t mask; - sigemptyset(&mask); - struct timespec timeout = {1, 0}; // 1 second - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(SigtimedwaitTest, ZeroTimeoutReturnsEAGAIN) { - sigset_t mask; - sigemptyset(&mask); - struct timespec timeout = {0, 0}; // 0 second - EXPECT_THAT(sigtimedwait(&mask, nullptr, &timeout), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(SigtimedwaitTest, KillGeneratedSIGCHLD) { - EXPECT_THAT(kill(getpid(), SIGCHLD), SyscallSucceeds()); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - struct timespec ts = {5, 0}; - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts), - SyscallSucceedsWithValue(SIGCHLD)); -} - -TEST(SigtimedwaitTest, ChildExitGeneratedSIGCHLD) { - pid_t pid = fork(); - if (pid == 0) { - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - struct timespec ts = {5, 0}; - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts), - SyscallSucceedsWithValue(SIGCHLD)); -} - -TEST(SigtimedwaitTest, ChildExitGeneratedSIGCHLDWithHandler) { - // Setup handler for SIGCHLD, but don't unblock it. - struct sigaction sa; - sa.sa_sigaction = NoopHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - const auto action_cleanup = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa)); - - pid_t pid = fork(); - if (pid == 0) { - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - struct timespec ts = {5, 0}; - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &ts), - SyscallSucceedsWithValue(SIGCHLD)); - - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; -} - -// sigtimedwait cannot catch SIGKILL. -TEST(SigtimedwaitTest, SIGKILLUncaught) { - // This is a regression test for sigtimedwait dequeuing SIGKILLs, thus - // preventing the task from exiting. - // - // The explanation below is specific to behavior in gVisor. The Linux behavior - // here is irrelevant because without a bug that prevents delivery of SIGKILL, - // none of this behavior is visible (in Linux or gVisor). - // - // SIGKILL is rather intrusive. Simply sending the SIGKILL marks - // ThreadGroup.exitStatus as exiting with SIGKILL, before the SIGKILL is even - // delivered. - // - // As a result, we cannot simply exit the child with a different exit code if - // it survives and expect to see that code in waitpid because: - // 1. PrepareGroupExit will override Task.exitStatus with - // ThreadGroup.exitStatus. - // 2. waitpid(2) will always return ThreadGroup.exitStatus rather than - // Task.exitStatus. - // - // We could use exit(2) to set Task.exitStatus without override, and a SIGCHLD - // handler to receive Task.exitStatus in the parent, but with that much - // test complexity, it is cleaner to simply use a pipe to notify the parent - // that we survived. - constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2); - - int pipe_fds[2]; - ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - FileDescriptor rfd(pipe_fds[0]); - FileDescriptor wfd(pipe_fds[1]); - - pid_t pid = fork(); - if (pid == 0) { - rfd.reset(); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGKILL); - RetryEINTR(sigtimedwait)(&mask, nullptr, nullptr); - - // Survived. - char c = 'a'; - TEST_PCHECK(WriteFd(wfd.get(), &c, 1) == 1); - _exit(1); - } - ASSERT_THAT(pid, SyscallSucceeds()); - - wfd.reset(); - - // Wait for child to block in sigtimedwait, then kill it. - absl::SleepFor(kSigtimedwaitSetupTime); - - // Sending SIGKILL will attempt to enqueue the signal twice: once in the - // normal signal sending path, and once to all Tasks in the ThreadGroup when - // applying SIGKILL side-effects. - // - // If we use kill(2), the former will be on the ThreadGroup signal queue and - // the latter will be on the Task signal queue. sigtimedwait can only dequeue - // one signal, so the other would kill the Task, masking bugs. - // - // If we use tkill(2), the former will be on the Task signal queue and the - // latter will be dropped as a duplicate. Then sigtimedwait can theoretically - // dequeue the single SIGKILL. - EXPECT_THAT(syscall(SYS_tkill, pid, SIGKILL), SyscallSucceeds()); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) << status; - - // Child shouldn't have survived. - char c; - EXPECT_THAT(ReadFd(rfd.get(), &c, 1), SyscallSucceedsWithValue(0)); -} - -TEST(SigtimedwaitTest, IgnoredUnmaskedSignal) { - constexpr int kSigno = SIGUSR1; - constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2); - constexpr auto kSigtimedwaitTimeout = absl::Seconds(5); - ASSERT_GT(kSigtimedwaitTimeout, kSigtimedwaitSetupTime); - - // Ensure that kSigno is ignored, and unmasked on this thread. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - const auto scoped_sigaction = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, mask)); - - // Create a thread which will send us kSigno while we are blocked in - // sigtimedwait. - pid_t tid = gettid(); - ScopedThread sigthread([&] { - absl::SleepFor(kSigtimedwaitSetupTime); - EXPECT_THAT(tgkill(getpid(), tid, kSigno), SyscallSucceeds()); - }); - - // sigtimedwait should not observe kSigno since it is ignored and already - // unmasked, causing it to be dropped before it is enqueued. - struct timespec timeout_ts = absl::ToTimespec(kSigtimedwaitTimeout); - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout_ts), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(SigtimedwaitTest, IgnoredMaskedSignal) { - constexpr int kSigno = SIGUSR1; - constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2); - constexpr auto kSigtimedwaitTimeout = absl::Seconds(5); - ASSERT_GT(kSigtimedwaitTimeout, kSigtimedwaitSetupTime); - - // Ensure that kSigno is ignored, and masked on this thread. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - const auto scoped_sigaction = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); - - // Create a thread which will send us kSigno while we are blocked in - // sigtimedwait. - pid_t tid = gettid(); - ScopedThread sigthread([&] { - absl::SleepFor(kSigtimedwaitSetupTime); - EXPECT_THAT(tgkill(getpid(), tid, kSigno), SyscallSucceeds()); - }); - - // sigtimedwait should observe kSigno since it is normally masked, causing it - // to be enqueued despite being ignored. - struct timespec timeout_ts = absl::ToTimespec(kSigtimedwaitTimeout); - EXPECT_THAT(RetryEINTR(sigtimedwait)(&mask, nullptr, &timeout_ts), - SyscallSucceedsWithValue(kSigno)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - // These tests depend on delivering SIGALRM/SIGCHLD to the main thread or in - // sigtimedwait. Block them so that any other threads created by TestInit will - // also have them blocked. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGALRM); - sigaddset(&set, SIGCHLD); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc deleted file mode 100644 index b616c2c87..000000000 --- a/test/syscalls/linux/socket.cc +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_umask.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// From linux/magic.h, but we can't depend on linux headers here. -#define SOCKFS_MAGIC 0x534F434B - -TEST(SocketTest, UnixSocketPairProtocol) { - int socks[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, socks), - SyscallSucceeds()); - close(socks[0]); - close(socks[1]); -} - -TEST(SocketTest, ProtocolUnix) { - struct { - int domain, type, protocol; - } tests[] = { - {AF_UNIX, SOCK_STREAM, PF_UNIX}, - {AF_UNIX, SOCK_SEQPACKET, PF_UNIX}, - {AF_UNIX, SOCK_DGRAM, PF_UNIX}, - }; - 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)); - } -} - -TEST(SocketTest, ProtocolInet) { - struct { - int domain, type, protocol; - } tests[] = { - {AF_INET, SOCK_DGRAM, IPPROTO_UDP}, - {AF_INET, SOCK_STREAM, IPPROTO_TCP}, - }; - 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)); - } -} - -TEST(SocketTest, UnixSocketStat) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor bound = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX)); - - // The permissions of the file created with bind(2) should be defined by the - // permissions of the bound socket and the umask. - mode_t sock_perm = 0765, mask = 0123; - ASSERT_THAT(fchmod(bound.get(), sock_perm), SyscallSucceeds()); - TempUmask m(mask); - - struct sockaddr_un addr = - ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX)); - ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - struct stat statbuf = {}; - ASSERT_THAT(stat(addr.sun_path, &statbuf), SyscallSucceeds()); - - // Mode should be S_IFSOCK. - EXPECT_EQ(statbuf.st_mode, S_IFSOCK | (sock_perm & ~mask)); - - // Timestamps should be equal and non-zero. - if (!IsRunningWithVFS1()) { - EXPECT_NE(statbuf.st_atime, 0); - EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime); - EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime); - } -} - -TEST(SocketTest, UnixSocketStatFS) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor bound = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX)); - - struct statfs st; - EXPECT_THAT(fstatfs(bound.get(), &st), SyscallSucceeds()); - EXPECT_EQ(st.f_type, SOCKFS_MAGIC); - EXPECT_EQ(st.f_bsize, getpagesize()); - EXPECT_EQ(st.f_namelen, NAME_MAX); -} - -TEST(SocketTest, UnixSCMRightsOnlyPassedOnce_NoRandomSave) { - const DisableSave ds; - - int sockets[2]; - ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); - // Send more than what will fit inside the send/receive buffers, so that it is - // split into multiple messages. - constexpr int kBufSize = 0x100000; - - pid_t pid = fork(); - if (pid == 0) { - TEST_PCHECK(close(sockets[0]) == 0); - - // Construct a message with some control message. - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int))] = {}; - std::vector<char> buf(kBufSize); - struct iovec iov = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - ((int*)CMSG_DATA(cmsg))[0] = sockets[1]; - - iov.iov_base = buf.data(); - iov.iov_len = kBufSize; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - int n = sendmsg(sockets[1], &msg, 0); - TEST_PCHECK(n == kBufSize); - TEST_PCHECK(shutdown(sockets[1], SHUT_RDWR) == 0); - TEST_PCHECK(close(sockets[1]) == 0); - _exit(0); - } - - close(sockets[1]); - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int))] = {}; - std::vector<char> buf(kBufSize); - struct iovec iov = {}; - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); - - iov.iov_base = buf.data(); - iov.iov_len = kBufSize; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - // The control message should only be present in the first message received. - int n; - ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds()); - ASSERT_GT(n, 0); - ASSERT_EQ(msg.msg_controllen, CMSG_SPACE(sizeof(int))); - - while (n > 0) { - ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds()); - ASSERT_EQ(msg.msg_controllen, 0); - } - - close(sockets[0]); - - int status; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); -} - -using SocketOpenTest = ::testing::TestWithParam<int>; - -// UDS cannot be opened. -TEST_P(SocketOpenTest, Unix) { - // FIXME(b/142001530): Open incorrectly succeeds on gVisor. - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor bound = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX)); - - struct sockaddr_un addr = - ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX)); - - ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - EXPECT_THAT(open(addr.sun_path, GetParam()), SyscallFailsWithErrno(ENXIO)); -} - -INSTANTIATE_TEST_SUITE_P(OpenModes, SocketOpenTest, - ::testing::Values(O_RDONLY, O_RDWR)); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_abstract.cc b/test/syscalls/linux/socket_abstract.cc deleted file mode 100644 index 00999f192..000000000 --- a/test/syscalls/linux/socket_abstract.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_generic.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/socket_unix.h" -#include "test/syscalls/linux/socket_unix_cmsg.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - AbstractUnixSockets, AllSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - AbstractUnixSockets, UnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - AbstractUnixSockets, UnixSocketPairCmsgTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_bind_to_device.cc b/test/syscalls/linux/socket_bind_to_device.cc deleted file mode 100644 index 6b27f6eab..000000000 --- a/test/syscalls/linux/socket_bind_to_device.cc +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> -#include <cstring> -#include <map> -#include <memory> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_bind_to_device_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -using std::string; - -// Test fixture for SO_BINDTODEVICE tests. -class BindToDeviceTest : public ::testing::TestWithParam<SocketKind> { - protected: - void SetUp() override { - printf("Testing case: %s\n", GetParam().description.c_str()); - ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) - << "CAP_NET_RAW is required to use SO_BINDTODEVICE"; - - interface_name_ = "eth1"; - auto interface_names = GetInterfaceNames(); - if (interface_names.find(interface_name_) == interface_names.end()) { - // Need a tunnel. - tunnel_ = ASSERT_NO_ERRNO_AND_VALUE(Tunnel::New()); - interface_name_ = tunnel_->GetName(); - ASSERT_FALSE(interface_name_.empty()); - } - socket_ = ASSERT_NO_ERRNO_AND_VALUE(GetParam().Create()); - } - - string interface_name() const { return interface_name_; } - - int socket_fd() const { return socket_->get(); } - - private: - std::unique_ptr<Tunnel> tunnel_; - string interface_name_; - std::unique_ptr<FileDescriptor> socket_; -}; - -constexpr char kIllegalIfnameChar = '/'; - -// Tests getsockopt of the default value. -TEST_P(BindToDeviceTest, GetsockoptDefault) { - char name_buffer[IFNAMSIZ * 2]; - char original_name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Read the default SO_BINDTODEVICE. - memset(original_name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - for (size_t i = 0; i <= sizeof(name_buffer); i++) { - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = i; - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, &name_buffer_size), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(name_buffer_size, 0); - EXPECT_EQ(memcmp(name_buffer, original_name_buffer, sizeof(name_buffer)), - 0); - } -} - -// Tests setsockopt of invalid device name. -TEST_P(BindToDeviceTest, SetsockoptInvalidDeviceName) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Set an invalid device name. - memset(name_buffer, kIllegalIfnameChar, 5); - name_buffer_size = 5; - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - name_buffer_size), - SyscallFailsWithErrno(ENODEV)); -} - -// Tests setsockopt of a buffer with a valid device name but not -// null-terminated, with different sizes of buffer. -TEST_P(BindToDeviceTest, SetsockoptValidDeviceNameWithoutNullTermination) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - strncpy(name_buffer, interface_name().c_str(), interface_name().size() + 1); - // Intentionally overwrite the null at the end. - memset(name_buffer + interface_name().size(), kIllegalIfnameChar, - sizeof(name_buffer) - interface_name().size()); - for (size_t i = 1; i <= sizeof(name_buffer); i++) { - name_buffer_size = i; - SCOPED_TRACE(absl::StrCat("Buffer size: ", i)); - // It should only work if the size provided is exactly right. - if (name_buffer_size == interface_name().size()) { - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, name_buffer_size), - SyscallSucceeds()); - } else { - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, name_buffer_size), - SyscallFailsWithErrno(ENODEV)); - } - } -} - -// Tests setsockopt of a buffer with a valid device name and null-terminated, -// with different sizes of buffer. -TEST_P(BindToDeviceTest, SetsockoptValidDeviceNameWithNullTermination) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - strncpy(name_buffer, interface_name().c_str(), interface_name().size() + 1); - // Don't overwrite the null at the end. - memset(name_buffer + interface_name().size() + 1, kIllegalIfnameChar, - sizeof(name_buffer) - interface_name().size() - 1); - for (size_t i = 1; i <= sizeof(name_buffer); i++) { - name_buffer_size = i; - SCOPED_TRACE(absl::StrCat("Buffer size: ", i)); - // It should only work if the size provided is at least the right size. - if (name_buffer_size >= interface_name().size()) { - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, name_buffer_size), - SyscallSucceeds()); - } else { - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, name_buffer_size), - SyscallFailsWithErrno(ENODEV)); - } - } -} - -// Tests that setsockopt of an invalid device name doesn't unset the previous -// valid setsockopt. -TEST_P(BindToDeviceTest, SetsockoptValidThenInvalid) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Write successfully. - strncpy(name_buffer, interface_name().c_str(), sizeof(name_buffer)); - ASSERT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - sizeof(name_buffer)), - SyscallSucceeds()); - - // Read it back successfully. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, interface_name().size() + 1); - EXPECT_STREQ(name_buffer, interface_name().c_str()); - - // Write unsuccessfully. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = 5; - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - sizeof(name_buffer)), - SyscallFailsWithErrno(ENODEV)); - - // Read it back successfully, it's unchanged. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, interface_name().size() + 1); - EXPECT_STREQ(name_buffer, interface_name().c_str()); -} - -// Tests that setsockopt of zero-length string correctly unsets the previous -// value. -TEST_P(BindToDeviceTest, SetsockoptValidThenClear) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Write successfully. - strncpy(name_buffer, interface_name().c_str(), sizeof(name_buffer)); - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - sizeof(name_buffer)), - SyscallSucceeds()); - - // Read it back successfully. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, interface_name().size() + 1); - EXPECT_STREQ(name_buffer, interface_name().c_str()); - - // Clear it successfully. - name_buffer_size = 0; - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - name_buffer_size), - SyscallSucceeds()); - - // Read it back successfully, it's cleared. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, 0); -} - -// Tests that setsockopt of empty string correctly unsets the previous -// value. -TEST_P(BindToDeviceTest, SetsockoptValidThenClearWithNull) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Write successfully. - strncpy(name_buffer, interface_name().c_str(), sizeof(name_buffer)); - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - sizeof(name_buffer)), - SyscallSucceeds()); - - // Read it back successfully. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, interface_name().size() + 1); - EXPECT_STREQ(name_buffer, interface_name().c_str()); - - // Clear it successfully. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer[0] = 0; - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - name_buffer_size), - SyscallSucceeds()); - - // Read it back successfully, it's cleared. - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = sizeof(name_buffer); - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, 0); -} - -// Tests getsockopt with different buffer sizes. -TEST_P(BindToDeviceTest, GetsockoptDevice) { - char name_buffer[IFNAMSIZ * 2]; - socklen_t name_buffer_size; - - // Write successfully. - strncpy(name_buffer, interface_name().c_str(), sizeof(name_buffer)); - ASSERT_THAT(setsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, name_buffer, - sizeof(name_buffer)), - SyscallSucceeds()); - - // Read it back at various buffer sizes. - for (size_t i = 0; i <= sizeof(name_buffer); i++) { - memset(name_buffer, kIllegalIfnameChar, sizeof(name_buffer)); - name_buffer_size = i; - SCOPED_TRACE(absl::StrCat("Buffer size: ", i)); - // Linux only allows a buffer at least IFNAMSIZ, even if less would suffice - // for this interface name. - if (name_buffer_size >= IFNAMSIZ) { - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, &name_buffer_size), - SyscallSucceeds()); - EXPECT_EQ(name_buffer_size, interface_name().size() + 1); - EXPECT_STREQ(name_buffer, interface_name().c_str()); - } else { - EXPECT_THAT(getsockopt(socket_fd(), SOL_SOCKET, SO_BINDTODEVICE, - name_buffer, &name_buffer_size), - SyscallFailsWithErrno(EINVAL)); - EXPECT_EQ(name_buffer_size, i); - } - } -} - -INSTANTIATE_TEST_SUITE_P(BindToDeviceTest, BindToDeviceTest, - ::testing::Values(IPv4UDPUnboundSocket(0), - IPv4TCPUnboundSocket(0))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_bind_to_device_distribution.cc b/test/syscalls/linux/socket_bind_to_device_distribution.cc deleted file mode 100644 index f8a0a80f2..000000000 --- a/test/syscalls/linux/socket_bind_to_device_distribution.cc +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <atomic> -#include <cstdio> -#include <cstring> -#include <map> -#include <memory> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_bind_to_device_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -using std::string; -using std::vector; - -struct EndpointConfig { - std::string bind_to_device; - double expected_ratio; -}; - -struct DistributionTestCase { - std::string name; - std::vector<EndpointConfig> endpoints; -}; - -struct ListenerConnector { - TestAddress listener; - TestAddress connector; -}; - -// Test fixture for SO_BINDTODEVICE tests the distribution of packets received -// with varying SO_BINDTODEVICE settings. -class BindToDeviceDistributionTest - : public ::testing::TestWithParam< - ::testing::tuple<ListenerConnector, DistributionTestCase>> { - protected: - void SetUp() override { - printf("Testing case: %s, listener=%s, connector=%s\n", - ::testing::get<1>(GetParam()).name.c_str(), - ::testing::get<0>(GetParam()).listener.description.c_str(), - ::testing::get<0>(GetParam()).connector.description.c_str()); - ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) - << "CAP_NET_RAW is required to use SO_BINDTODEVICE"; - } -}; - -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -// Binds sockets to different devices and then creates many TCP connections. -// Checks that the distribution of connections received on the sockets matches -// the expectation. -TEST_P(BindToDeviceDistributionTest, Tcp) { - auto const& [listener_connector, test] = GetParam(); - - TestAddress const& listener = listener_connector.listener; - TestAddress const& connector = listener_connector.connector; - sockaddr_storage listen_addr = listener.addr; - sockaddr_storage conn_addr = connector.addr; - - auto interface_names = GetInterfaceNames(); - - // Create the listening sockets. - std::vector<FileDescriptor> listener_fds; - std::vector<std::unique_ptr<Tunnel>> all_tunnels; - for (auto const& endpoint : test.endpoints) { - if (!endpoint.bind_to_device.empty() && - interface_names.find(endpoint.bind_to_device) == - interface_names.end()) { - all_tunnels.push_back( - ASSERT_NO_ERRNO_AND_VALUE(Tunnel::New(endpoint.bind_to_device))); - interface_names.insert(endpoint.bind_to_device); - } - - listener_fds.push_back(ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP))); - int fd = listener_fds.back().get(); - - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, - endpoint.bind_to_device.c_str(), - endpoint.bind_to_device.size() + 1), - SyscallSucceeds()); - ASSERT_THAT( - bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(fd, 40), SyscallSucceeds()); - - // On the first bind we need to determine which port was bound. - if (listener_fds.size() > 1) { - continue; - } - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listener_fds[0].get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - } - - constexpr int kConnectAttempts = 10000; - std::atomic<int> connects_received = ATOMIC_VAR_INIT(0); - std::vector<int> accept_counts(listener_fds.size(), 0); - std::vector<std::unique_ptr<ScopedThread>> listen_threads( - listener_fds.size()); - - 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]() { - do { - auto fd = Accept(listener_fds[i].get(), nullptr, nullptr); - if (!fd.ok()) { - // Another thread has shutdown our read side causing the accept to - // fail. - ASSERT_GE(connects_received, kConnectAttempts) - << "errno = " << fd.error(); - return; - } - // Receive some data from a socket to be sure that the connect() - // system call has been completed on another side. - // Do a short read and then close the socket to trigger a RST. This - // ensures that both ends of the connection are cleaned up and no - // goroutines hang around in TIME-WAIT. We do this so that this test - // does not timeout under gotsan runs where lots of goroutines can - // cause the test to use absurd amounts of memory. - // - // See: https://tools.ietf.org/html/rfc2525#page-50 section 2.17 - uint16_t data; - EXPECT_THAT( - RetryEINTR(recv)(fd.ValueOrDie().get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(sizeof(data))); - accept_counts[i]++; - } while (++connects_received < kConnectAttempts); - - // Shutdown all sockets to wake up other threads. - for (auto const& listener_fd : listener_fds) { - shutdown(listener_fd.get(), SHUT_RDWR); - } - }); - } - - for (int32_t i = 0; i < kConnectAttempts; i++) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT( - RetryEINTR(connect)(fd.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - } - - // Join threads to be sure that all connections have been counted. - for (auto const& listen_thread : listen_threads) { - listen_thread->Join(); - } - // Check that connections are distributed correctly among listening sockets. - for (long unsigned int i = 0; i < accept_counts.size(); i++) { - EXPECT_THAT( - accept_counts[i], - EquivalentWithin(static_cast<int>(kConnectAttempts * - test.endpoints[i].expected_ratio), - 0.10)) - << "endpoint " << i << " got the wrong number of packets"; - } -} - -// Binds sockets to different devices and then sends many UDP packets. Checks -// that the distribution of packets received on the sockets matches the -// expectation. -TEST_P(BindToDeviceDistributionTest, Udp) { - auto const& [listener_connector, test] = GetParam(); - - TestAddress const& listener = listener_connector.listener; - TestAddress const& connector = listener_connector.connector; - sockaddr_storage listen_addr = listener.addr; - sockaddr_storage conn_addr = connector.addr; - - auto interface_names = GetInterfaceNames(); - - // Create the listening socket. - std::vector<FileDescriptor> listener_fds; - std::vector<std::unique_ptr<Tunnel>> all_tunnels; - for (auto const& endpoint : test.endpoints) { - if (!endpoint.bind_to_device.empty() && - interface_names.find(endpoint.bind_to_device) == - interface_names.end()) { - all_tunnels.push_back( - ASSERT_NO_ERRNO_AND_VALUE(Tunnel::New(endpoint.bind_to_device))); - interface_names.insert(endpoint.bind_to_device); - } - - listener_fds.push_back( - ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0))); - int fd = listener_fds.back().get(); - - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, - endpoint.bind_to_device.c_str(), - endpoint.bind_to_device.size() + 1), - SyscallSucceeds()); - ASSERT_THAT( - bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), - SyscallSucceeds()); - - // On the first bind we need to determine which port was bound. - if (listener_fds.size() > 1) { - continue; - } - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listener_fds[0].get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - } - - constexpr int kConnectAttempts = 10000; - std::atomic<int> packets_received = ATOMIC_VAR_INIT(0); - std::vector<int> packets_per_socket(listener_fds.size(), 0); - std::vector<std::unique_ptr<ScopedThread>> receiver_threads( - listener_fds.size()); - - 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 { - struct sockaddr_storage addr = {}; - socklen_t addrlen = sizeof(addr); - int data; - - auto ret = RetryEINTR(recvfrom)( - listener_fds[i].get(), &data, sizeof(data), 0, - reinterpret_cast<struct sockaddr*>(&addr), &addrlen); - - if (packets_received < kConnectAttempts) { - ASSERT_THAT(ret, SyscallSucceedsWithValue(sizeof(data))); - } - - if (ret != sizeof(data)) { - // Another thread may have shutdown our read side causing the - // recvfrom to fail. - break; - } - - packets_received++; - packets_per_socket[i]++; - - // A response is required to synchronize with the main thread, - // otherwise the main thread can send more than can fit into receive - // queues. - EXPECT_THAT(RetryEINTR(sendto)( - listener_fds[i].get(), &data, sizeof(data), 0, - reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceedsWithValue(sizeof(data))); - } while (packets_received < kConnectAttempts); - - // Shutdown all sockets to wake up other threads. - for (auto const& listener_fd : listener_fds) { - shutdown(listener_fd.get(), SHUT_RDWR); - } - }); - } - - for (int i = 0; i < kConnectAttempts; i++) { - FileDescriptor const fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0)); - EXPECT_THAT(RetryEINTR(sendto)(fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceedsWithValue(sizeof(i))); - int data; - EXPECT_THAT(RetryEINTR(recv)(fd.get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(sizeof(data))); - } - - // Join threads to be sure that all connections have been counted. - for (auto const& receiver_thread : receiver_threads) { - receiver_thread->Join(); - } - // Check that packets are distributed correctly among listening sockets. - for (long unsigned int i = 0; i < packets_per_socket.size(); i++) { - EXPECT_THAT( - packets_per_socket[i], - EquivalentWithin(static_cast<int>(kConnectAttempts * - test.endpoints[i].expected_ratio), - 0.10)) - << "endpoint " << i << " got the wrong number of packets"; - } -} - -std::vector<DistributionTestCase> GetDistributionTestCases() { - return std::vector<DistributionTestCase>{ - {"Even distribution among sockets not bound to device", - {{"", 1. / 3}, {"", 1. / 3}, {"", 1. / 3}}}, - {"Sockets bound to other interfaces get no packets", - {{"eth1", 0}, {"", 1. / 2}, {"", 1. / 2}}}, - {"Bound has priority over unbound", {{"eth1", 0}, {"", 0}, {"lo", 1}}}, - {"Even distribution among sockets bound to device", - {{"eth1", 0}, {"lo", 1. / 2}, {"lo", 1. / 2}}}, - }; -} - -INSTANTIATE_TEST_SUITE_P( - BindToDeviceTest, BindToDeviceDistributionTest, - ::testing::Combine(::testing::Values( - // Listeners bound to IPv4 addresses refuse - // connections using IPv6 addresses. - ListenerConnector{V4Any(), V4Loopback()}, - ListenerConnector{V4Loopback(), V4MappedLoopback()}), - ::testing::ValuesIn(GetDistributionTestCases()))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_bind_to_device_sequence.cc b/test/syscalls/linux/socket_bind_to_device_sequence.cc deleted file mode 100644 index d3cc71dbf..000000000 --- a/test/syscalls/linux/socket_bind_to_device_sequence.cc +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> -#include <cstring> -#include <map> -#include <memory> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/container/node_hash_map.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_bind_to_device_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -using std::string; -using std::vector; - -// Test fixture for SO_BINDTODEVICE tests the results of sequences of socket -// binding. -class BindToDeviceSequenceTest : public ::testing::TestWithParam<SocketKind> { - protected: - void SetUp() override { - printf("Testing case: %s\n", GetParam().description.c_str()); - ASSERT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) - << "CAP_NET_RAW is required to use SO_BINDTODEVICE"; - socket_factory_ = GetParam(); - - interface_names_ = GetInterfaceNames(); - } - - PosixErrorOr<std::unique_ptr<FileDescriptor>> NewSocket() const { - return socket_factory_.Create(); - } - - // Gets a device by device_id. If the device_id has been seen before, returns - // the previously returned device. If not, finds or creates a new device. - // Returns an empty string on failure. - void GetDevice(int device_id, string* device_name) { - auto device = devices_.find(device_id); - if (device != devices_.end()) { - *device_name = device->second; - return; - } - - // Need to pick a new device. Try ethernet first. - *device_name = absl::StrCat("eth", next_unused_eth_); - if (interface_names_.find(*device_name) != interface_names_.end()) { - devices_[device_id] = *device_name; - next_unused_eth_++; - return; - } - - // Need to make a new tunnel device. gVisor tests should have enough - // ethernet devices to never reach here. - ASSERT_FALSE(IsRunningOnGvisor()); - // Need a tunnel. - tunnels_.push_back(ASSERT_NO_ERRNO_AND_VALUE(Tunnel::New())); - devices_[device_id] = tunnels_.back()->GetName(); - *device_name = devices_[device_id]; - } - - // Release the socket - void ReleaseSocket(int socket_id) { - // Close the socket that was made in a previous action. The socket_id - // indicates which socket to close based on index into the list of actions. - sockets_to_close_.erase(socket_id); - } - - // SetDevice changes the bind_to_device option. It does not bind or re-bind. - void SetDevice(int socket_id, int device_id) { - auto socket_fd = sockets_to_close_[socket_id]->get(); - string device_name; - ASSERT_NO_FATAL_FAILURE(GetDevice(device_id, &device_name)); - EXPECT_THAT(setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, - device_name.c_str(), device_name.size() + 1), - SyscallSucceedsWithValue(0)); - } - - // Bind a socket with the reuse options and bind_to_device options. Checks - // that all steps succeed and that the bind command's error matches want. - // Sets the socket_id to uniquely identify the socket bound if it is not - // nullptr. - void BindSocket(bool reuse_port, bool reuse_addr, int device_id = 0, - int want = 0, int* socket_id = nullptr) { - next_socket_id_++; - sockets_to_close_[next_socket_id_] = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket_fd = sockets_to_close_[next_socket_id_]->get(); - if (socket_id != nullptr) { - *socket_id = next_socket_id_; - } - - // If reuse_port is indicated, do that. - if (reuse_port) { - EXPECT_THAT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - } - - // If reuse_addr is indicated, do that. - if (reuse_addr) { - EXPECT_THAT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - } - - // If the device is non-zero, bind to that device. - if (device_id != 0) { - string device_name; - ASSERT_NO_FATAL_FAILURE(GetDevice(device_id, &device_name)); - EXPECT_THAT(setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, - device_name.c_str(), device_name.size() + 1), - SyscallSucceedsWithValue(0)); - char get_device[100]; - socklen_t get_device_size = 100; - EXPECT_THAT(getsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, get_device, - &get_device_size), - SyscallSucceedsWithValue(0)); - } - - struct sockaddr_in addr = {}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr.sin_port = port_; - if (want == 0) { - ASSERT_THAT( - bind(socket_fd, reinterpret_cast<const struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - } else { - ASSERT_THAT( - bind(socket_fd, reinterpret_cast<const struct sockaddr*>(&addr), - sizeof(addr)), - SyscallFailsWithErrno(want)); - } - - if (port_ == 0) { - // We don't yet know what port we'll be using so we need to fetch it and - // remember it for future commands. - socklen_t addr_size = sizeof(addr); - ASSERT_THAT( - getsockname(socket_fd, reinterpret_cast<struct sockaddr*>(&addr), - &addr_size), - SyscallSucceeds()); - port_ = addr.sin_port; - } - } - - private: - SocketKind socket_factory_; - // devices maps from the device id in the test case to the name of the device. - absl::node_hash_map<int, string> devices_; - // These are the tunnels that were created for the test and will be destroyed - // by the destructor. - vector<std::unique_ptr<Tunnel>> tunnels_; - // A list of all interface names before the test started. - std::unordered_set<string> interface_names_; - // The next ethernet device to use when requested a device. - int next_unused_eth_ = 1; - // The port for all tests. Originally 0 (any) and later set to the port that - // all further commands will use. - in_port_t port_ = 0; - // sockets_to_close_ is a map from action index to the socket that was - // created. - absl::node_hash_map<int, - std::unique_ptr<gvisor::testing::FileDescriptor>> - sockets_to_close_; - int next_socket_id_ = 0; -}; - -TEST_P(BindToDeviceSequenceTest, BindTwiceWithDeviceFails) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ false, /* bind_to_device */ 3)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 3, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindToDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ false, /* bind_to_device */ 1)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ false, /* bind_to_device */ 2)); -} - -TEST_P(BindToDeviceSequenceTest, BindToDeviceAndThenWithoutDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindWithoutDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindWithDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 456, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 789, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindWithReuse) { - ASSERT_NO_FATAL_FAILURE( - BindSocket(/* reusePort */ true, /* reuse_addr */ false)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, - /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 0)); -} - -TEST_P(BindToDeviceSequenceTest, BindingWithReuseAndDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 456)); - ASSERT_NO_FATAL_FAILURE( - BindSocket(/* reuse_port */ true, /* reuse_addr */ false)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 789)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 999, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, MixingReuseAndNotReuseByBindingToDevice) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 123, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 456, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 789, 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 999, 0)); -} - -TEST_P(BindToDeviceSequenceTest, CannotBindTo0AfterMixingReuseAndNotReuse) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 456)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindAndRelease) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 123)); - int to_release; - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, 0, &to_release)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 345, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 789)); - // Release the bind to device 0 and try again. - ASSERT_NO_FATAL_FAILURE(ReleaseSocket(to_release)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 345)); -} - -TEST_P(BindToDeviceSequenceTest, BindTwiceWithReuseOnce) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindWithReuseAddr) { - ASSERT_NO_FATAL_FAILURE( - BindSocket(/* reusePort */ false, /* reuse_addr */ true)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 123, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ true, /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ true, /* bind_to_device */ 0)); -} - -TEST_P(BindToDeviceSequenceTest, - CannotBindTo0AfterMixingReuseAddrAndNotReuseAddr) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 123)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 456)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindReuseAddrReusePortThenReusePort) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindReuseAddrReusePortThenReuseAddr) { - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindDoubleReuseAddrReusePortThenReusePort) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ true, /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindDoubleReuseAddrReusePortThenReuseAddr) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ true, /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindReusePortThenReuseAddrReusePort) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ true, /* reuse_addr */ false, /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ true, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, BindReuseAddrThenReuseAddr) { - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ true, /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0, EADDRINUSE)); -} - -TEST_P(BindToDeviceSequenceTest, - BindReuseAddrThenReuseAddrReusePortThenReuseAddr) { - // The behavior described in this test seems like a Linux bug. It doesn't - // make any sense and it is unlikely that any applications rely on it. - // - // Both SO_REUSEADDR and SO_REUSEPORT allow binding multiple UDP sockets to - // the same address and deliver each packet to exactly one of the bound - // sockets. If both are enabled, one of the strategies is selected to route - // packets. The strategy is selected dynamically based on the settings of the - // currently bound sockets. Usually, the strategy is selected based on the - // common setting (SO_REUSEADDR or SO_REUSEPORT) amongst the sockets, but for - // some reason, Linux allows binding sets of sockets with no overlapping - // settings in some situations. In this case, it is not obvious which strategy - // would be selected as the configured setting is a contradiction. - SKIP_IF(IsRunningOnGvisor()); - - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ true, /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ true, - /* bind_to_device */ 0)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true, - /* reuse_addr */ false, - /* bind_to_device */ 0)); -} - -// Repro test for gvisor.dev/issue/1217. Not replicated in ports_test.go as this -// test is different from the others and wouldn't fit well there. -TEST_P(BindToDeviceSequenceTest, BindAndReleaseDifferentDevice) { - int to_release; - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 3, 0, &to_release)); - ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false, - /* reuse_addr */ false, - /* bind_to_device */ 3, EADDRINUSE)); - // Change the device. Since the socket was already bound, this should have no - // effect. - SetDevice(to_release, 2); - // Release the bind to device 3 and try again. - ASSERT_NO_FATAL_FAILURE(ReleaseSocket(to_release)); - ASSERT_NO_FATAL_FAILURE(BindSocket( - /* reuse_port */ false, /* reuse_addr */ false, /* bind_to_device */ 3)); -} - -INSTANTIATE_TEST_SUITE_P(BindToDeviceTest, BindToDeviceSequenceTest, - ::testing::Values(IPv4UDPUnboundSocket(0), - IPv4TCPUnboundSocket(0))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_bind_to_device_util.cc b/test/syscalls/linux/socket_bind_to_device_util.cc deleted file mode 100644 index f4ee775bd..000000000 --- a/test/syscalls/linux/socket_bind_to_device_util.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_bind_to_device_util.h" - -#include <arpa/inet.h> -#include <fcntl.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> -#include <unistd.h> - -#include <cstdio> -#include <cstring> -#include <map> -#include <memory> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -using std::string; - -PosixErrorOr<std::unique_ptr<Tunnel>> Tunnel::New(string tunnel_name) { - int fd; - RETURN_ERROR_IF_SYSCALL_FAIL(fd = open("/dev/net/tun", O_RDWR)); - - // Using `new` to access a non-public constructor. - auto new_tunnel = absl::WrapUnique(new Tunnel(fd)); - - ifreq ifr = {}; - ifr.ifr_flags = IFF_TUN; - strncpy(ifr.ifr_name, tunnel_name.c_str(), sizeof(ifr.ifr_name)); - - RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd, TUNSETIFF, &ifr)); - new_tunnel->name_ = ifr.ifr_name; - return new_tunnel; -} - -std::unordered_set<string> GetInterfaceNames() { - struct if_nameindex* interfaces = if_nameindex(); - std::unordered_set<string> names; - if (interfaces == nullptr) { - return names; - } - for (auto interface = interfaces; - interface->if_index != 0 || interface->if_name != nullptr; interface++) { - names.insert(interface->if_name); - } - if_freenameindex(interfaces); - return names; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_bind_to_device_util.h b/test/syscalls/linux/socket_bind_to_device_util.h deleted file mode 100644 index f941ccc86..000000000 --- a/test/syscalls/linux/socket_bind_to_device_util.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_SOCKET_BIND_TO_DEVICE_UTILS_H_ -#define GVISOR_TEST_SYSCALLS_SOCKET_BIND_TO_DEVICE_UTILS_H_ - -#include <arpa/inet.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> -#include <unistd.h> - -#include <cstdio> -#include <cstring> -#include <map> -#include <memory> -#include <string> -#include <unordered_map> -#include <unordered_set> -#include <utility> -#include <vector> - -#include "absl/memory/memory.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -class Tunnel { - public: - static PosixErrorOr<std::unique_ptr<Tunnel>> New( - std::string tunnel_name = ""); - const std::string& GetName() const { return name_; } - - ~Tunnel() { - if (fd_ != -1) { - close(fd_); - } - } - - private: - Tunnel(int fd) : fd_(fd) {} - int fd_ = -1; - std::string name_; -}; - -std::unordered_set<std::string> GetInterfaceNames(); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_SOCKET_BIND_TO_DEVICE_UTILS_H_ diff --git a/test/syscalls/linux/socket_blocking.cc b/test/syscalls/linux/socket_blocking.cc deleted file mode 100644 index 7e88aa2d9..000000000 --- a/test/syscalls/linux/socket_blocking.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_blocking.h" - -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(BlockingSocketPairTest, RecvBlocks) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - constexpr auto kDuration = absl::Milliseconds(200); - auto before = Now(CLOCK_MONOTONIC); - - const ScopedThread t([&]() { - absl::SleepFor(kDuration); - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - }); - - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - auto after = Now(CLOCK_MONOTONIC); - EXPECT_GE(after - before, kDuration); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_blocking.h b/test/syscalls/linux/socket_blocking.h deleted file mode 100644 index db26e5ef5..000000000 --- a/test/syscalls/linux/socket_blocking.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of blocking connected sockets. -using BlockingSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_BLOCKING_H_ diff --git a/test/syscalls/linux/socket_capability.cc b/test/syscalls/linux/socket_capability.cc deleted file mode 100644 index 84b5b2b21..000000000 --- a/test/syscalls/linux/socket_capability.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Subset of socket tests that need Linux-specific headers (compared to POSIX -// headers). - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST(SocketTest, UnixConnectNeedsWritePerm) { - SKIP_IF(IsRunningWithVFS1()); - - FileDescriptor bound = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX)); - - struct sockaddr_un addr = - ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX)); - ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - ASSERT_THAT(listen(bound.get(), 1), SyscallSucceeds()); - - // Drop capabilites that allow us to override permision checks. Otherwise if - // the test is run as root, the connect below will bypass permission checks - // and succeed unexpectedly. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - // Connect should fail without write perms. - ASSERT_THAT(chmod(addr.sun_path, 0500), SyscallSucceeds()); - FileDescriptor client = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX)); - ASSERT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallFailsWithErrno(EACCES)); - - // Connect should succeed with write perms. - ASSERT_THAT(chmod(addr.sun_path, 0200), SyscallSucceeds()); - EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_filesystem.cc b/test/syscalls/linux/socket_filesystem.cc deleted file mode 100644 index 287359363..000000000 --- a/test/syscalls/linux/socket_filesystem.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_generic.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/socket_unix.h" -#include "test/syscalls/linux/socket_unix_cmsg.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - FilesystemUnixSockets, AllSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - FilesystemUnixSockets, UnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - FilesystemUnixSockets, UnixSocketPairCmsgTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_generic.h b/test/syscalls/linux/socket_generic.h deleted file mode 100644 index 00ae7bfc3..000000000 --- a/test/syscalls/linux/socket_generic.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of blocking and non-blocking -// connected stream sockets. -using AllSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_GENERIC_H_ diff --git a/test/syscalls/linux/socket_generic_stress.cc b/test/syscalls/linux/socket_generic_stress.cc deleted file mode 100644 index c35aa2183..000000000 --- a/test/syscalls/linux/socket_generic_stress.cc +++ /dev/null @@ -1,299 +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. - -#include <poll.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <unistd.h> - -#include <array> -#include <string> - -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -constexpr char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; - -PosixErrorOr<int> NumPorts() { - int min = 0; - int max = 1 << 16; - - // Read the ephemeral range from /proc. - ASSIGN_OR_RETURN_ERRNO(std::string rangefile, GetContents(kRangeFile)); - const std::string err_msg = - absl::StrFormat("%s has invalid content: %s", kRangeFile, rangefile); - if (rangefile.back() != '\n') { - return PosixError(EINVAL, err_msg); - } - rangefile.pop_back(); - std::vector<std::string> range = - absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); - if (range.size() < 2 || !absl::SimpleAtoi(range.front(), &min) || - !absl::SimpleAtoi(range.back(), &max)) { - return PosixError(EINVAL, err_msg); - } - - // If we can open as writable, limit the range. - if (!access(kRangeFile, W_OK)) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, - Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); - max = min + 50; - const std::string small_range = absl::StrFormat("%d %d", min, max); - int n = write(fd.get(), small_range.c_str(), small_range.size()); - if (n < 0) { - return PosixError( - errno, - absl::StrFormat("write(%d [%s], \"%s\", %d)", fd.get(), kRangeFile, - small_range.c_str(), small_range.size())); - } - } - return max - min; -} - -// Test fixture for tests that apply to pairs of connected sockets. -using ConnectStressTest = SocketPairTest; - -TEST_P(ConnectStressTest, Reset) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); - for (int i = 0; i < nports * 2; i++) { - const std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Send some data to ensure that the connection gets reset and the port gets - // released immediately. This avoids either end entering TIME-WAIT. - char sent_data[100] = {}; - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - // Poll the other FD to make sure that the data is in the receive buffer - // before closing it to ensure a RST is triggered. - const int kTimeout = 10000; - struct pollfd pfd = { - .fd = sockets->second_fd(), - .events = POLL_IN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - } -} - -// Tests that opening too many connections -- without closing them -- does lead -// to port exhaustion. -TEST_P(ConnectStressTest, TooManyOpen) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); - int err_num = 0; - std::vector<std::unique_ptr<SocketPair>> sockets = - std::vector<std::unique_ptr<SocketPair>>(nports); - for (int i = 0; i < nports * 2; i++) { - PosixErrorOr<std::unique_ptr<SocketPair>> socks = NewSocketPair(); - if (!socks.ok()) { - err_num = socks.error().errno_value(); - break; - } - sockets.push_back(std::move(socks).ValueOrDie()); - } - ASSERT_EQ(err_num, EADDRINUSE); -} - -INSTANTIATE_TEST_SUITE_P( - AllConnectedSockets, ConnectStressTest, - ::testing::Values(IPv6UDPBidirectionalBindSocketPair(0), - IPv4UDPBidirectionalBindSocketPair(0), - DualStackUDPBidirectionalBindSocketPair(0), - - // Without REUSEADDR, we get port exhaustion on Linux. - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn)(IPv6TCPAcceptBindSocketPair(0)), - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn)(IPv4TCPAcceptBindSocketPair(0)), - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &kSockOptOn)( - DualStackTCPAcceptBindSocketPair(0)))); - -// Test fixture for tests that apply to pairs of connected sockets created with -// a persistent listener (if applicable). -class PersistentListenerConnectStressTest : public SocketPairTest { - protected: - PersistentListenerConnectStressTest() : slept_{false} {} - - // NewSocketSleep is the same as NewSocketPair, but will sleep once (over the - // lifetime of the fixture) and retry if creation fails due to EADDRNOTAVAIL. - PosixErrorOr<std::unique_ptr<SocketPair>> NewSocketSleep() { - // We can't reuse a connection too close in time to its last use, as TCP - // uses the timestamp difference to disambiguate connections. With a - // sufficiently small port range, we'll cycle through too quickly, and TCP - // won't allow for connection reuse. Thus, we sleep the first time - // encountering EADDRINUSE to allow for that difference (1 second in - // gVisor). - PosixErrorOr<std::unique_ptr<SocketPair>> socks = NewSocketPair(); - if (socks.ok()) { - return socks; - } - if (!slept_ && socks.error().errno_value() == EADDRNOTAVAIL) { - absl::SleepFor(absl::Milliseconds(1500)); - slept_ = true; - return NewSocketPair(); - } - return socks; - } - - private: - bool slept_; -}; - -TEST_P(PersistentListenerConnectStressTest, ShutdownCloseFirst) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); - for (int i = 0; i < nports * 2; i++) { - std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds()); - if (GetParam().type == SOCK_STREAM) { - // Poll the other FD to make sure that we see the FIN from the other - // side before closing the second_fd. This ensures that the first_fd - // enters TIME-WAIT and not second_fd. - const int kTimeout = 10000; - struct pollfd pfd = { - .fd = sockets->second_fd(), - .events = POLL_IN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - } - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RDWR), SyscallSucceeds()); - } -} - -TEST_P(PersistentListenerConnectStressTest, ShutdownCloseSecond) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); - for (int i = 0; i < nports * 2; i++) { - const std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RDWR), SyscallSucceeds()); - if (GetParam().type == SOCK_STREAM) { - // Poll the other FD to make sure that we see the FIN from the other - // side before closing the first_fd. This ensures that the second_fd - // enters TIME-WAIT and not first_fd. - const int kTimeout = 10000; - struct pollfd pfd = { - .fd = sockets->first_fd(), - .events = POLL_IN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - } - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds()); - } -} - -TEST_P(PersistentListenerConnectStressTest, Close) { - const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); - for (int i = 0; i < nports * 2; i++) { - std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); - } -} - -INSTANTIATE_TEST_SUITE_P( - AllConnectedSockets, PersistentListenerConnectStressTest, - ::testing::Values( - IPv6UDPBidirectionalBindSocketPair(0), - IPv4UDPBidirectionalBindSocketPair(0), - DualStackUDPBidirectionalBindSocketPair(0), - - // Without REUSEADDR, we get port exhaustion on Linux. - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &kSockOptOn)( - IPv6TCPAcceptBindPersistentListenerSocketPair(0)), - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &kSockOptOn)( - IPv4TCPAcceptBindPersistentListenerSocketPair(0)), - SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &kSockOptOn)( - DualStackTCPAcceptBindPersistentListenerSocketPair(0)))); - -using DataTransferStressTest = SocketPairTest; - -TEST_P(DataTransferStressTest, BigDataTransfer) { - // TODO(b/165912341): These are too slow on KVM platform with nested virt. - SKIP_IF(GvisorPlatform() == Platform::kKVM); - - const std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int client_fd = sockets->first_fd(); - int server_fd = sockets->second_fd(); - - ScopedThread echo([server_fd]() { - std::array<uint8_t, 1024> buf; - for (;;) { - ssize_t r = read(server_fd, buf.data(), buf.size()); - ASSERT_THAT(r, SyscallSucceeds()); - if (r == 0) { - break; - } - for (size_t i = 0; i < r;) { - ssize_t w = write(server_fd, buf.data() + i, r - i); - ASSERT_GE(w, 0); - i += w; - } - } - ASSERT_THAT(shutdown(server_fd, SHUT_WR), SyscallSucceeds()); - }); - - const std::string chunk = "Though this upload be but little, it is fierce."; - std::string big_string; - while (big_string.size() < 31 << 20) { - big_string += chunk; - } - absl::string_view data = big_string; - - ScopedThread writer([client_fd, data]() { - absl::string_view view = data; - while (!view.empty()) { - ssize_t n = write(client_fd, view.data(), view.size()); - ASSERT_GE(n, 0); - view = view.substr(n); - } - ASSERT_THAT(shutdown(client_fd, SHUT_WR), SyscallSucceeds()); - }); - - std::string buf; - buf.resize(1 << 20); - while (!data.empty()) { - ssize_t n = read(client_fd, buf.data(), buf.size()); - ASSERT_GE(n, 0); - for (size_t i = 0; i < n; i += chunk.size()) { - size_t c = std::min(chunk.size(), n - i); - ASSERT_EQ(buf.substr(i, c), data.substr(i, c)) << "offset " << i; - } - data = data.substr(n); - } - // Should read EOF now. - ASSERT_THAT(read(client_fd, buf.data(), buf.size()), - SyscallSucceedsWithValue(0)); -} - -INSTANTIATE_TEST_SUITE_P( - AllConnectedSockets, DataTransferStressTest, - ::testing::Values(IPv6TCPAcceptBindPersistentListenerSocketPair(0), - IPv4TCPAcceptBindPersistentListenerSocketPair(0), - DualStackTCPAcceptBindPersistentListenerSocketPair(0))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_generic_test_cases.cc b/test/syscalls/linux/socket_generic_test_cases.cc deleted file mode 100644 index 5c4cb6c35..000000000 --- a/test/syscalls/linux/socket_generic_test_cases.cc +++ /dev/null @@ -1,903 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_generic.h" - -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -// This file is a generic socket test file. It must be built with another file -// that provides the test types. - -namespace gvisor { -namespace testing { - -TEST_P(AllSocketPairTest, BasicReadWrite) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[20]; - const std::string data = "abc"; - ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), 3), - SyscallSucceedsWithValue(3)); - ASSERT_THAT(ReadFd(sockets->second_fd(), buf, 3), - SyscallSucceedsWithValue(3)); - EXPECT_EQ(data, absl::string_view(buf, 3)); -} - -TEST_P(AllSocketPairTest, BasicReadWriteBadBuffer) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - const std::string data = "abc"; - ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), 3), - SyscallSucceedsWithValue(3)); - ASSERT_THAT(ReadFd(sockets->second_fd(), nullptr, 3), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_P(AllSocketPairTest, BasicSendRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(AllSocketPairTest, BasicSendmmsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[200]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - std::vector<struct mmsghdr> msgs(10); - std::vector<struct iovec> iovs(msgs.size()); - const int chunk_size = sizeof(sent_data) / msgs.size(); - for (size_t i = 0; i < msgs.size(); i++) { - iovs[i].iov_len = chunk_size; - iovs[i].iov_base = &sent_data[i * chunk_size]; - msgs[i].msg_hdr.msg_iov = &iovs[i]; - msgs[i].msg_hdr.msg_iovlen = 1; - } - - ASSERT_THAT( - RetryEINTR(sendmmsg)(sockets->first_fd(), &msgs[0], msgs.size(), 0), - SyscallSucceedsWithValue(msgs.size())); - - for (const struct mmsghdr& msg : msgs) { - EXPECT_EQ(chunk_size, msg.msg_len); - } - - char received_data[sizeof(sent_data)]; - for (size_t i = 0; i < msgs.size(); i++) { - ASSERT_THAT(ReadFd(sockets->second_fd(), &received_data[i * chunk_size], - chunk_size), - SyscallSucceedsWithValue(chunk_size)); - } - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(AllSocketPairTest, SendmmsgIsLimitedByMAXIOV) { - std::unique_ptr<SocketPair> sockets = - ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char c = 0; - - std::vector<struct mmsghdr> msgs(UIO_MAXIOV + 1); - std::vector<struct iovec> iovs(msgs.size()); - for (size_t i = 0; i < msgs.size(); i++) { - iovs[i].iov_len = 1; - iovs[i].iov_base = &c; - msgs[i].msg_hdr.msg_iov = &iovs[i]; - msgs[i].msg_hdr.msg_iovlen = 1; - } - - int n; - ASSERT_THAT(n = RetryEINTR(sendmmsg)(sockets->first_fd(), msgs.data(), - msgs.size(), MSG_DONTWAIT), - SyscallSucceeds()); - EXPECT_LE(n, UIO_MAXIOV); -} - -TEST_P(AllSocketPairTest, BasicRecvmmsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[200]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - char received_data[sizeof(sent_data)]; - std::vector<struct mmsghdr> msgs(10); - std::vector<struct iovec> iovs(msgs.size()); - const int chunk_size = sizeof(sent_data) / msgs.size(); - for (size_t i = 0; i < msgs.size(); i++) { - iovs[i].iov_len = chunk_size; - iovs[i].iov_base = &received_data[i * chunk_size]; - msgs[i].msg_hdr.msg_iov = &iovs[i]; - msgs[i].msg_hdr.msg_iovlen = 1; - } - - for (size_t i = 0; i < msgs.size(); i++) { - ASSERT_THAT( - WriteFd(sockets->first_fd(), &sent_data[i * chunk_size], chunk_size), - SyscallSucceedsWithValue(chunk_size)); - } - - ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->second_fd(), &msgs[0], msgs.size(), - 0, nullptr), - SyscallSucceedsWithValue(msgs.size())); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - for (const struct mmsghdr& msg : msgs) { - EXPECT_EQ(chunk_size, msg.msg_len); - } -} - -TEST_P(AllSocketPairTest, SendmsgRecvmsg10KB) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - std::vector<char> sent_data(10 * 1024); - RandomizeBuffer(sent_data.data(), sent_data.size()); - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size())); - - std::vector<char> received_data(sent_data.size()); - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->second_fd(), received_data.data(), - received_data.size())); - - EXPECT_EQ(0, - memcmp(sent_data.data(), received_data.data(), sent_data.size())); -} - -// This test validates that a sendmsg/recvmsg w/ MSG_CTRUNC is a no-op on -// input flags. -TEST_P(AllSocketPairTest, SendmsgRecvmsgMsgCtruncNoop) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - std::vector<char> sent_data(10 * 1024); - RandomizeBuffer(sent_data.data(), sent_data.size()); - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size())); - - std::vector<char> received_data(sent_data.size()); - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - iov.iov_base = &received_data[0]; - iov.iov_len = received_data.size(); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - // MSG_CTRUNC should be a no-op. - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CTRUNC), - SyscallSucceedsWithValue(received_data.size())); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - EXPECT_EQ(cmsg, nullptr); - EXPECT_EQ(msg.msg_controllen, 0); - EXPECT_EQ(0, - memcmp(sent_data.data(), received_data.data(), sent_data.size())); -} - -TEST_P(AllSocketPairTest, SendmsgRecvmsg16KB) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - std::vector<char> sent_data(16 * 1024); - RandomizeBuffer(sent_data.data(), sent_data.size()); - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data.data(), sent_data.size())); - - std::vector<char> received_data(sent_data.size()); - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->second_fd(), received_data.data(), - received_data.size())); - - EXPECT_EQ(0, - memcmp(sent_data.data(), received_data.data(), sent_data.size())); -} - -TEST_P(AllSocketPairTest, RecvmsgMsghdrFlagsNotClearedOnFailure) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char received_data[10] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); - - // Check that msghdr flags were not changed. - EXPECT_EQ(msg.msg_flags, -1); -} - -TEST_P(AllSocketPairTest, RecvmsgMsghdrFlagsCleared) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data)] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data))); - - // Check that msghdr flags were cleared. - EXPECT_EQ(msg.msg_flags, 0); -} - -TEST_P(AllSocketPairTest, RecvmsgPeekMsghdrFlagsCleared) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data)] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_PEEK), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data))); - - // Check that msghdr flags were cleared. - EXPECT_EQ(msg.msg_flags, 0); -} - -TEST_P(AllSocketPairTest, RecvmsgIovNotUpdated) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) * 2] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(sent_data))); - - // Check that the iovec length was not updated. - EXPECT_EQ(msg.msg_iov->iov_len, sizeof(received_data)); -} - -TEST_P(AllSocketPairTest, RecvmmsgInvalidTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[10]; - struct mmsghdr msg = {}; - struct iovec iov = {}; - iov.iov_len = sizeof(buf); - iov.iov_base = buf; - msg.msg_hdr.msg_iov = &iov; - msg.msg_hdr.msg_iovlen = 1; - struct timespec timeout = {-1, -1}; - ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->first_fd(), &msg, 1, 0, &timeout), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(AllSocketPairTest, RecvmmsgTimeoutBeforeRecv) { - // There is a known bug in the Linux recvmmsg(2) causing it to block forever - // if the timeout expires while blocking for the first message. - SKIP_IF(!IsRunningOnGvisor()); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[10]; - struct mmsghdr msg = {}; - struct iovec iov = {}; - iov.iov_len = sizeof(buf); - iov.iov_base = buf; - msg.msg_hdr.msg_iov = &iov; - msg.msg_hdr.msg_iovlen = 1; - struct timespec timeout = {}; - ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->first_fd(), &msg, 1, 0, &timeout), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, MsgPeek) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[50]; - memset(&sent_data, 0, sizeof(sent_data)); - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data)]; - for (int i = 0; i < 3; i++) { - memset(received_data, 0, sizeof(received_data)); - EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_PEEK), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); - } - - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); -} - -TEST_P(AllSocketPairTest, LingerSocketOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - struct linger got_linger = {-1, -1}; - socklen_t length = sizeof(struct linger); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &length), - SyscallSucceedsWithValue(0)); - struct linger want_linger = {}; - EXPECT_EQ(0, memcmp(&want_linger, &got_linger, sizeof(struct linger))); - EXPECT_EQ(sizeof(struct linger), length); -} - -TEST_P(AllSocketPairTest, KeepAliveSocketOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int keepalive = -1; - socklen_t length = sizeof(int); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, - &keepalive, &length), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, keepalive); - EXPECT_EQ(sizeof(int), length); -} - -TEST_P(AllSocketPairTest, RcvBufSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int size = 0; - socklen_t size_size = sizeof(size); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &size, &size_size), - SyscallSucceeds()); - EXPECT_GT(size, 0); -} - -TEST_P(AllSocketPairTest, GetSndBufSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int size = 0; - socklen_t size_size = sizeof(size); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &size, &size_size), - SyscallSucceeds()); - EXPECT_GT(size, 0); -} - -TEST_P(AllSocketPairTest, RecvTimeoutReadSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - EXPECT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutRecvSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutRecvOneSecondSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 1, .tv_usec = 0 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutRecvmsgSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - struct msghdr msg = {}; - char buf[20] = {}; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, SendTimeoutDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - timeval actual_tv = {.tv_sec = -1, .tv_usec = -1}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv_sec, 0); - EXPECT_EQ(actual_tv.tv_usec, 0); -} - -TEST_P(AllSocketPairTest, SetGetSendTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // tv_usec should be a multiple of 4000 to work on most systems. - timeval tv = {.tv_sec = 89, .tv_usec = 44000}; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - timeval actual_tv = {}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv_sec, tv.tv_sec); - EXPECT_EQ(actual_tv.tv_usec, tv.tv_usec); -} - -TEST_P(AllSocketPairTest, SetGetSendTimeoutLargerArg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval_with_extra { - struct timeval tv; - int64_t extra_data; - } ABSL_ATTRIBUTE_PACKED; - - // tv_usec should be a multiple of 4000 to work on most systems. - timeval_with_extra tv_extra = { - .tv = {.tv_sec = 0, .tv_usec = 124000}, - }; - - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, - &tv_extra, sizeof(tv_extra)), - SyscallSucceeds()); - - timeval_with_extra actual_tv = {}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv.tv_sec, tv_extra.tv.tv_sec); - EXPECT_EQ(actual_tv.tv.tv_usec, tv_extra.tv.tv_usec); -} - -TEST_P(AllSocketPairTest, SendTimeoutAllowsWrite) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(AllSocketPairTest, SendTimeoutAllowsSend) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(AllSocketPairTest, SendTimeoutAllowsSendmsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - ASSERT_NO_FATAL_FAILURE(SendNullCmsg(sockets->first_fd(), buf, sizeof(buf))); -} - -TEST_P(AllSocketPairTest, RecvTimeoutDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - timeval actual_tv = {.tv_sec = -1, .tv_usec = -1}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv_sec, 0); - EXPECT_EQ(actual_tv.tv_usec, 0); -} - -TEST_P(AllSocketPairTest, SetGetRecvTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - timeval tv = {.tv_sec = 123, .tv_usec = 456000}; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - timeval actual_tv = {}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv_sec, 123); - EXPECT_EQ(actual_tv.tv_usec, 456000); -} - -TEST_P(AllSocketPairTest, SetGetRecvTimeoutLargerArg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval_with_extra { - struct timeval tv; - int64_t extra_data; - } ABSL_ATTRIBUTE_PACKED; - - timeval_with_extra tv_extra = { - .tv = {.tv_sec = 0, .tv_usec = 432000}, - }; - - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, - &tv_extra, sizeof(tv_extra)), - SyscallSucceeds()); - - timeval_with_extra actual_tv = {}; - socklen_t len = sizeof(actual_tv); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, - &actual_tv, &len), - SyscallSucceeds()); - EXPECT_EQ(actual_tv.tv.tv_sec, 0); - EXPECT_EQ(actual_tv.tv.tv_usec, 432000); -} - -TEST_P(AllSocketPairTest, RecvTimeoutRecvmsgOneSecondSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 1, .tv_usec = 0 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - struct msghdr msg = {}; - char buf[20] = {}; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutUsecTooLarge) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 2000000 // 2 seconds. - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallFailsWithErrno(EDOM)); -} - -TEST_P(AllSocketPairTest, SendTimeoutUsecTooLarge) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 2000000 // 2 seconds. - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallFailsWithErrno(EDOM)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutUsecNeg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = -1 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallFailsWithErrno(EDOM)); -} - -TEST_P(AllSocketPairTest, SendTimeoutUsecNeg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = -1 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallFailsWithErrno(EDOM)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutNegSecRead) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = -1, .tv_usec = 0 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - EXPECT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutNegSecRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = -1, .tv_usec = 0 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - char buf[20] = {}; - EXPECT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutNegSecRecvmsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = -1, .tv_usec = 0 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - struct msghdr msg = {}; - char buf[20] = {}; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - EXPECT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvWaitAll) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_WAITALL), - SyscallSucceedsWithValue(sizeof(sent_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(AllSocketPairTest, RecvWaitAllDontWait) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char data[100] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), data, sizeof(data), - MSG_WAITALL | MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(AllSocketPairTest, RecvTimeoutWaitAll) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 200000 // 200ms - }; - EXPECT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, - sizeof(tv)), - SyscallSucceeds()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) * 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_WAITALL), - SyscallSucceedsWithValue(sizeof(sent_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(AllSocketPairTest, GetSockoptType) { - int type = GetParam().type; - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - for (const int fd : {sockets->first_fd(), sockets->second_fd()}) { - int opt; - socklen_t optlen = sizeof(opt); - EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_TYPE, &opt, &optlen), - SyscallSucceeds()); - - // Type may have SOCK_NONBLOCK and SOCK_CLOEXEC ORed into it. Remove these - // before comparison. - type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC); - EXPECT_EQ(opt, type) << absl::StrFormat( - "getsockopt(%d, SOL_SOCKET, SO_TYPE, &opt, &optlen) => opt=%d was " - "unexpected", - fd, opt); - } -} - -TEST_P(AllSocketPairTest, GetSockoptDomain) { - const int domain = GetParam().domain; - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - for (const int fd : {sockets->first_fd(), sockets->second_fd()}) { - int opt; - socklen_t optlen = sizeof(opt); - EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &opt, &optlen), - SyscallSucceeds()); - EXPECT_EQ(opt, domain) << absl::StrFormat( - "getsockopt(%d, SOL_SOCKET, SO_DOMAIN, &opt, &optlen) => opt=%d was " - "unexpected", - fd, opt); - } -} - -TEST_P(AllSocketPairTest, GetSockoptProtocol) { - const int protocol = GetParam().protocol; - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - for (const int fd : {sockets->first_fd(), sockets->second_fd()}) { - int opt; - socklen_t optlen = sizeof(opt); - EXPECT_THAT(getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &opt, &optlen), - SyscallSucceeds()); - EXPECT_EQ(opt, protocol) << absl::StrFormat( - "getsockopt(%d, SOL_SOCKET, SO_PROTOCOL, &opt, &optlen) => opt=%d was " - "unexpected", - fd, opt); - } -} - -TEST_P(AllSocketPairTest, SetAndGetBooleanSocketOptions) { - int sock_opts[] = {SO_BROADCAST, SO_PASSCRED, SO_NO_CHECK, - SO_REUSEADDR, SO_REUSEPORT, SO_KEEPALIVE}; - for (int sock_opt : sock_opts) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int enable = -1; - socklen_t enableLen = sizeof(enable); - - // Test that the option is initially set to false. - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, - &enableLen), - SyscallSucceeds()); - ASSERT_EQ(enableLen, sizeof(enable)); - EXPECT_EQ(enable, 0) << absl::StrFormat( - "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d", - sock_opt, enable); - - // Test that setting the option to true is reflected in the subsequent - // call to getsockopt(2). - enable = 1; - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, - sizeof(enable)), - SyscallSucceeds()); - enable = -1; - enableLen = sizeof(enable); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable, - &enableLen), - SyscallSucceeds()); - ASSERT_EQ(enableLen, sizeof(enable)); - EXPECT_EQ(enable, 1) << absl::StrFormat( - "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d", - sock_opt, enable); - } -} - -TEST_P(AllSocketPairTest, GetSocketOutOfBandInlineOption) { - // We do not support disabling this option. It is always enabled. - SKIP_IF(!IsRunningOnGvisor()); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int enable = -1; - socklen_t enableLen = sizeof(enable); - - int want = 1; - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &enable, - &enableLen), - SyscallSucceeds()); - ASSERT_EQ(enableLen, sizeof(enable)); - EXPECT_EQ(enable, want); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc deleted file mode 100644 index 597b5bcb1..000000000 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ /dev/null @@ -1,2934 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <poll.h> -#include <string.h> -#include <sys/socket.h> - -#include <atomic> -#include <iostream> -#include <memory> -#include <string> -#include <tuple> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/strings/str_cat.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::Gt; - -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -struct TestParam { - TestAddress listener; - TestAddress connector; -}; - -std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) { - return absl::StrCat("Listen", info.param.listener.description, "_Connect", - info.param.connector.description); -} - -using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>; - -TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) { - int fd[2] = {}; - - // Valid AF but invalid for socketpair(2) return ESOCKTNOSUPPORT. - ASSERT_THAT(socketpair(AF_INET, 0, 0, fd), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - ASSERT_THAT(socketpair(AF_INET6, 0, 0, fd), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - - // Invalid AF will fail. - ASSERT_THAT(socketpair(AF_MAX, 0, 0, fd), SyscallFails()); - ASSERT_THAT(socketpair(8675309, 0, 0, fd), SyscallFails()); -} - -enum class Operation { - Bind, - Connect, - SendTo, -}; - -std::string OperationToString(Operation operation) { - switch (operation) { - case Operation::Bind: - return "Bind"; - case Operation::Connect: - return "Connect"; - // Operation::SendTo is the default. - default: - return "SendTo"; - } -} - -using OperationSequence = std::vector<Operation>; - -using DualStackSocketTest = - ::testing::TestWithParam<std::tuple<TestAddress, OperationSequence>>; - -TEST_P(DualStackSocketTest, AddressOperations) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_DGRAM, 0)); - - const TestAddress& addr = std::get<0>(GetParam()); - const OperationSequence& operations = std::get<1>(GetParam()); - - auto addr_in = reinterpret_cast<const sockaddr*>(&addr.addr); - - // sockets may only be bound once. Both `connect` and `sendto` cause a socket - // to be bound. - bool bound = false; - for (const Operation& operation : operations) { - bool sockname = false; - bool peername = false; - switch (operation) { - case Operation::Bind: { - ASSERT_NO_ERRNO(SetAddrPort( - addr.family(), const_cast<sockaddr_storage*>(&addr.addr), 0)); - - int bind_ret = bind(fd.get(), addr_in, addr.addr_len); - - // Dual stack sockets may only be bound to AF_INET6. - if (!bound && addr.family() == AF_INET6) { - EXPECT_THAT(bind_ret, SyscallSucceeds()); - bound = true; - - sockname = true; - } else { - EXPECT_THAT(bind_ret, SyscallFailsWithErrno(EINVAL)); - } - break; - } - case Operation::Connect: { - ASSERT_NO_ERRNO(SetAddrPort( - addr.family(), const_cast<sockaddr_storage*>(&addr.addr), 1337)); - - EXPECT_THAT(RetryEINTR(connect)(fd.get(), addr_in, addr.addr_len), - SyscallSucceeds()) - << GetAddrStr(addr_in); - bound = true; - - sockname = true; - peername = true; - - break; - } - case Operation::SendTo: { - const char payload[] = "hello"; - ASSERT_NO_ERRNO(SetAddrPort( - addr.family(), const_cast<sockaddr_storage*>(&addr.addr), 1337)); - - ssize_t sendto_ret = sendto(fd.get(), &payload, sizeof(payload), 0, - addr_in, addr.addr_len); - - EXPECT_THAT(sendto_ret, SyscallSucceedsWithValue(sizeof(payload))); - sockname = !bound; - bound = true; - break; - } - } - - if (sockname) { - sockaddr_storage sock_addr; - socklen_t addrlen = sizeof(sock_addr); - ASSERT_THAT(getsockname(fd.get(), reinterpret_cast<sockaddr*>(&sock_addr), - &addrlen), - SyscallSucceeds()); - ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6)); - - auto sock_addr_in6 = reinterpret_cast<const sockaddr_in6*>(&sock_addr); - - if (operation == Operation::SendTo) { - EXPECT_EQ(sock_addr_in6->sin6_family, AF_INET6); - EXPECT_TRUE(IN6_IS_ADDR_UNSPECIFIED(sock_addr_in6->sin6_addr.s6_addr32)) - << OperationToString(operation) << " getsocknam=" - << GetAddrStr(reinterpret_cast<sockaddr*>(&sock_addr)); - - EXPECT_NE(sock_addr_in6->sin6_port, 0); - } else if (IN6_IS_ADDR_V4MAPPED( - reinterpret_cast<const sockaddr_in6*>(addr_in) - ->sin6_addr.s6_addr32)) { - EXPECT_TRUE(IN6_IS_ADDR_V4MAPPED(sock_addr_in6->sin6_addr.s6_addr32)) - << OperationToString(operation) << " getsocknam=" - << GetAddrStr(reinterpret_cast<sockaddr*>(&sock_addr)); - } - } - - if (peername) { - sockaddr_storage peer_addr; - socklen_t addrlen = sizeof(peer_addr); - ASSERT_THAT(getpeername(fd.get(), reinterpret_cast<sockaddr*>(&peer_addr), - &addrlen), - SyscallSucceeds()); - ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6)); - - if (addr.family() == AF_INET || - IN6_IS_ADDR_V4MAPPED(reinterpret_cast<const sockaddr_in6*>(addr_in) - ->sin6_addr.s6_addr32)) { - EXPECT_TRUE(IN6_IS_ADDR_V4MAPPED( - reinterpret_cast<const sockaddr_in6*>(&peer_addr) - ->sin6_addr.s6_addr32)) - << OperationToString(operation) << " getpeername=" - << GetAddrStr(reinterpret_cast<sockaddr*>(&peer_addr)); - } - } - } -} - -// TODO(gvisor.dev/issue/1556): uncomment V4MappedAny. -INSTANTIATE_TEST_SUITE_P( - All, DualStackSocketTest, - ::testing::Combine( - ::testing::Values(V4Any(), V4Loopback(), /*V4MappedAny(),*/ - V4MappedLoopback(), V6Any(), V6Loopback()), - ::testing::ValuesIn<OperationSequence>( - {{Operation::Bind, Operation::Connect, Operation::SendTo}, - {Operation::Bind, Operation::SendTo, Operation::Connect}, - {Operation::Connect, Operation::Bind, Operation::SendTo}, - {Operation::Connect, Operation::SendTo, Operation::Bind}, - {Operation::SendTo, Operation::Bind, Operation::Connect}, - {Operation::SendTo, Operation::Connect, Operation::Bind}})), - [](::testing::TestParamInfo< - std::tuple<TestAddress, OperationSequence>> const& info) { - const TestAddress& addr = std::get<0>(info.param); - const OperationSequence& operations = std::get<1>(info.param); - std::string s = addr.description; - for (const Operation& operation : operations) { - absl::StrAppend(&s, OperationToString(operation)); - } - return s; - }); - -void tcpSimpleConnectTest(TestAddress const& listener, - TestAddress const& connector, bool unbound) { - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - if (!unbound) { - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - } - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - // - // We have to assign a name to the accepted socket, as unamed temporary - // objects are destructed upon full evaluation of the expression it is in, - // potentially causing the connecting socket to fail to shutdown properly. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - ASSERT_THAT(shutdown(listen_fd.get(), SHUT_RDWR), SyscallSucceeds()); - - ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds()); -} - -TEST_P(SocketInetLoopbackTest, TCP) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - tcpSimpleConnectTest(listener, connector, true); -} - -TEST_P(SocketInetLoopbackTest, TCPListenUnbound) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - tcpSimpleConnectTest(listener, connector, false); -} - -TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) { - const auto& param = GetParam(); - - const TestAddress& listener = param.listener; - const TestAddress& connector = param.connector; - - constexpr int kBacklog = 5; - - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - ASSERT_THAT(shutdown(listen_fd.get(), SHUT_RD), SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - const uint16_t port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - - // TODO(b/157236388): Remove Disable save after bug is fixed. S/R test can - // fail because the last socket may not be delivered to the accept queue - // by the time connect returns. - DisableSave ds; - for (int i = 0; i < kBacklog; i++) { - auto client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(RetryEINTR(connect)(client.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - } - for (int i = 0; i < kBacklog; i++) { - ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), SyscallSucceeds()); - } -} - -TEST_P(SocketInetLoopbackTest, TCPListenShutdown) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - constexpr int kBacklog = 2; - constexpr int kFDs = kBacklog + 1; - - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - - // Shutdown the write of the listener, expect to not have any effect. - ASSERT_THAT(shutdown(listen_fd.get(), SHUT_WR), SyscallSucceeds()); - - for (int i = 0; i < kFDs; i++) { - auto client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(RetryEINTR(connect)(client.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), SyscallSucceeds()); - } - - // Shutdown the read of the listener, expect to fail subsequent - // server accepts, binds and client connects. - ASSERT_THAT(shutdown(listen_fd.get(), SHUT_RD), SyscallSucceeds()); - - ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), - SyscallFailsWithErrno(EINVAL)); - - // Check that shutdown did not release the port. - FileDescriptor new_listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT( - bind(new_listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Check that subsequent connection attempts receive a RST. - auto client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - for (int i = 0; i < kFDs; i++) { - auto client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(RetryEINTR(connect)(client.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallFailsWithErrno(ECONNREFUSED)); - } -} - -TEST_P(SocketInetLoopbackTest, TCPListenClose) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - constexpr int kAcceptCount = 2; - constexpr int kBacklog = kAcceptCount + 2; - constexpr int kFDs = kBacklog * 3; - - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - std::vector<FileDescriptor> clients; - for (int i = 0; i < kFDs; i++) { - auto client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len); - if (ret != 0) { - EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); - } - clients.push_back(std::move(client)); - } - for (int i = 0; i < kAcceptCount; i++) { - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - } -} - -void TestListenWhileConnect(const TestParam& param, - void (*stopListen)(FileDescriptor&)) { - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - constexpr int kBacklog = 2; - // Linux completes one more connection than the listen backlog argument. - // To ensure that there is at least one client connection that stays in - // connecting state, keep 2 more client connections than the listen backlog. - // gVisor differs in this behavior though, gvisor.dev/issue/3153. - constexpr int kClients = kBacklog + 2; - - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - std::vector<FileDescriptor> clients; - for (int i = 0; i < kClients; i++) { - FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len); - if (ret != 0) { - EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); - clients.push_back(std::move(client)); - } - } - - stopListen(listen_fd); - - for (auto& client : clients) { - constexpr int kTimeout = 10000; - pollfd pfd = { - .fd = client.get(), - .events = POLLIN, - }; - // When the listening socket is closed, then we expect the remote to reset - // the connection. - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR); - char c; - // Subsequent read can fail with: - // ECONNRESET: If the client connection was established and was reset by the - // remote. - // ECONNREFUSED: If the client connection failed to be established. - ASSERT_THAT(read(client.get(), &c, sizeof(c)), - AnyOf(SyscallFailsWithErrno(ECONNRESET), - SyscallFailsWithErrno(ECONNREFUSED))); - // The last client connection would be in connecting (SYN_SENT) state. - if (client.get() == clients[kClients - 1].get()) { - ASSERT_EQ(errno, ECONNREFUSED) << strerror(errno); - } - } -} - -TEST_P(SocketInetLoopbackTest, TCPListenCloseWhileConnect) { - TestListenWhileConnect(GetParam(), [](FileDescriptor& f) { - ASSERT_THAT(close(f.release()), SyscallSucceeds()); - }); -} - -TEST_P(SocketInetLoopbackTest, TCPListenShutdownWhileConnect) { - TestListenWhileConnect(GetParam(), [](FileDescriptor& f) { - ASSERT_THAT(shutdown(f.get(), SHUT_RD), SyscallSucceeds()); - }); -} - -// TODO(b/157236388): Remove _NoRandomSave once bug is fixed. Test fails w/ -// random save as established connections which can't be delivered to the accept -// queue because the queue is full are not correctly delivered after restore -// causing the last accept to timeout on the restore. -TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - constexpr int kBacklogSize = 2; - ASSERT_THAT(listen(listen_fd.get(), kBacklogSize), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - int i = 0; - while (1) { - int ret; - - // Connect to the listening socket. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ret = connect(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len); - if (ret != 0) { - EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); - pollfd pfd = { - .fd = conn_fd.get(), - .events = POLLOUT, - }; - ret = poll(&pfd, 1, 3000); - if (ret == 0) break; - EXPECT_THAT(ret, SyscallSucceedsWithValue(1)); - } - EXPECT_THAT(RetryEINTR(send)(conn_fd.get(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - ASSERT_THAT(shutdown(conn_fd.get(), SHUT_RDWR), SyscallSucceeds()); - i++; - } - - for (; i != 0; i--) { - // Accept the connection. - // - // We have to assign a name to the accepted socket, as unamed temporary - // objects are destructed upon full evaluation of the expression it is in, - // potentially causing the connecting socket to fail to shutdown properly. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - } -} - -// Test if the stack completes atmost listen backlog number of client -// connections. It exercises the path of the stack that enqueues completed -// connections to accept queue vs new incoming SYNs. -TEST_P(SocketInetLoopbackTest, TCPConnectBacklog_NoRandomSave) { - const auto& param = GetParam(); - const TestAddress& listener = param.listener; - const TestAddress& connector = param.connector; - - constexpr int kBacklog = 1; - // Keep the number of client connections more than the listen backlog. - // Linux completes one more connection than the listen backlog argument. - // gVisor differs in this behavior though, gvisor.dev/issue/3153. - int kClients = kBacklog + 2; - if (IsRunningOnGvisor()) { - kClients--; - } - - // Run the following test for few iterations to test race between accept queue - // getting filled with incoming SYNs. - for (int num = 0; num < 10; num++) { - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - - std::vector<FileDescriptor> clients; - // Issue multiple non-blocking client connects. - for (int i = 0; i < kClients; i++) { - FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len); - if (ret != 0) { - EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS)); - } - clients.push_back(std::move(client)); - } - - // Now that client connects are issued, wait for the accept queue to get - // filled and ensure no new client connection is completed. - for (int i = 0; i < kClients; i++) { - pollfd pfd = { - .fd = clients[i].get(), - .events = POLLOUT, - }; - if (i < kClients - 1) { - // Poll for client side connection completions with a large timeout. - // We cannot poll on the listener side without calling accept as poll - // stays level triggered with non-zero accept queue length. - // - // Client side poll would not guarantee that the completed connection - // has been enqueued in to the acccept queue, but the fact that the - // listener ACKd the SYN, means that it cannot complete any new incoming - // SYNs when it has already ACKd for > backlog number of SYNs. - ASSERT_THAT(poll(&pfd, 1, 10000), SyscallSucceedsWithValue(1)) - << "num=" << num << " i=" << i << " kClients=" << kClients; - ASSERT_EQ(pfd.revents, POLLOUT) << "num=" << num << " i=" << i; - } else { - // Now that we expect accept queue filled up, ensure that the last - // client connection never completes with a smaller poll timeout. - ASSERT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(0)) - << "num=" << num << " i=" << i; - } - - ASSERT_THAT(close(clients[i].release()), SyscallSucceedsWithValue(0)) - << "num=" << num << " i=" << i; - } - clients.clear(); - // We close the listening side and open a new listener. We could instead - // drain the accept queue by calling accept() and reuse the listener, but - // that is racy as the retransmitted SYNs could get ACKd as we make room in - // the accept queue. - ASSERT_THAT(close(listen_fd.release()), SyscallSucceedsWithValue(0)); - } -} - -// TCPFinWait2Test creates a pair of connected sockets then closes one end to -// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local -// IP/port on a new socket and tries to connect. The connect should fail w/ -// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the -// connect again with a new socket and this time it should succeed. -// -// TCP timers are not S/R today, this can cause this test to be flaky when run -// under random S/R due to timer being reset on a restore. -TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - // Lower FIN_WAIT2 state to 5 seconds for test. - constexpr int kTCPLingerTimeout = 5; - EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, - &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), - SyscallSucceedsWithValue(0)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - sockaddr_storage conn_bound_addr; - socklen_t conn_addrlen = connector.addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - &conn_addrlen), - SyscallSucceeds()); - - // close the connecting FD to trigger FIN_WAIT2 on the connected fd. - conn_fd.reset(); - - // Now bind and connect a new socket. - const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - // Disable cooperative saves after this point. As a save between the first - // bind/connect and the second one can cause the linger timeout timer to - // be restarted causing the final bind/connect to fail. - DisableSave ds; - - ASSERT_THAT(bind(conn_fd2.get(), - reinterpret_cast<sockaddr*>(&conn_bound_addr), conn_addrlen), - SyscallFailsWithErrno(EADDRINUSE)); - - // Sleep for a little over the linger timeout to reduce flakiness in - // save/restore tests. - absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2)); - - ds.reset(); - - ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - conn_addrlen), - SyscallSucceeds()); -} - -// TCPLinger2TimeoutAfterClose creates a pair of connected sockets -// then closes one end to trigger FIN_WAIT2 state for the closed endpont. -// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/ -// connecting the same address succeeds. -// -// TCP timers are not S/R today, this can cause this test to be flaky when run -// under random S/R due to timer being reset on a restore. -TEST_P(SocketInetLoopbackTest, TCPLinger2TimeoutAfterClose_NoRandomSave) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - sockaddr_storage conn_bound_addr; - socklen_t conn_addrlen = connector.addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - &conn_addrlen), - SyscallSucceeds()); - - // Disable cooperative saves after this point as TCP timers are not restored - // across a S/R. - { - DisableSave ds; - constexpr int kTCPLingerTimeout = 5; - EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2, - &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), - SyscallSucceedsWithValue(0)); - - // close the connecting FD to trigger FIN_WAIT2 on the connected fd. - conn_fd.reset(); - - absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1)); - - // ds going out of scope will Re-enable S/R's since at this point the timer - // must have fired and cleaned up the endpoint. - } - - // Now bind and connect a new socket and verify that we can immediately - // rebind the address bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT(bind(conn_fd2.get(), - reinterpret_cast<sockaddr*>(&conn_bound_addr), conn_addrlen), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - conn_addrlen), - SyscallSucceeds()); -} - -// TCPResetAfterClose creates a pair of connected sockets then closes -// one end to trigger FIN_WAIT2 state for the closed endpoint verifies -// that we generate RSTs for any new data after the socket is fully -// closed. -TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // close the connecting FD to trigger FIN_WAIT2 on the connected fd. - conn_fd.reset(); - - int data = 1234; - - // Now send data which should trigger a RST as the other end should - // have timed out and closed the socket. - EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0), - SyscallSucceeds()); - // Sleep for a shortwhile to get a RST back. - absl::SleepFor(absl::Seconds(1)); - - // Try writing again and we should get an EPIPE back. - EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0), - SyscallFailsWithErrno(EPIPE)); - - // Trying to read should return zero as the other end did send - // us a FIN. We do it twice to verify that the RST does not cause an - // ECONNRESET on the read after EOF has been read by applicaiton. - EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(0)); -} - -// setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state. -// Callers can choose to perform active close on either ends of the connection -// and also specify if they want to enabled SO_REUSEADDR. -void setupTimeWaitClose(const TestAddress* listener, - const TestAddress* connector, bool reuse, - bool accept_close, sockaddr_storage* listen_addr, - sockaddr_storage* conn_bound_addr) { - // Create the listening socket. - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP)); - if (reuse) { - ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - } - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(listen_addr), - listener->addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener->addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(listen_addr), &addrlen), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP)); - - // We disable saves after this point as a S/R causes the netstack seed - // to be regenerated which changes what ports/ISN is picked for a given - // tuple (src ip,src port, dst ip, dst port). This can cause the final - // SYN to use a sequence number that looks like one from the current - // connection in TIME_WAIT and will not be accepted causing the test - // to timeout. - // - // TODO(gvisor.dev/issue/940): S/R portSeed/portHint - DisableSave ds; - - sockaddr_storage conn_addr = connector->addr; - ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector->addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Get the address/port bound by the connecting socket. - socklen_t conn_addrlen = connector->addr_len; - ASSERT_THAT( - getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(conn_bound_addr), - &conn_addrlen), - SyscallSucceeds()); - - FileDescriptor active_closefd, passive_closefd; - if (accept_close) { - active_closefd = std::move(accepted); - passive_closefd = std::move(conn_fd); - } else { - active_closefd = std::move(conn_fd); - passive_closefd = std::move(accepted); - } - - // shutdown to trigger TIME_WAIT. - ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); - { - constexpr int kTimeout = 10000; - pollfd pfd = { - .fd = passive_closefd.get(), - .events = POLLIN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - ASSERT_EQ(pfd.revents, POLLIN); - } - ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds()); - { - constexpr int kTimeout = 10000; - constexpr int16_t want_events = POLLHUP; - pollfd pfd = { - .fd = active_closefd.get(), - .events = want_events, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - } - - // This sleep is needed to reduce flake to ensure that the passive-close - // ensures the state transitions to CLOSE from LAST_ACK. - absl::SleepFor(absl::Seconds(1)); -} - -// These tests are disabled under random save as the the restore run -// results in the stack.Seed() being different which can cause -// sequence number of final connect to be one that is considered -// old and can cause the test to be flaky. -// -// Test re-binding of client and server bound addresses when the older -// connection is in TIME_WAIT. -TEST_P(SocketInetLoopbackTest, TCPPassiveCloseNoTimeWaitTest_NoRandomSave) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, - true /*accept_close*/, &listen_addr, &conn_bound_addr); - - // Now bind a new socket and verify that we can immediately rebind the address - // bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(bind(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - param.connector.addr_len), - SyscallSucceeds()); - - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - param.listener.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketInetLoopbackTest, - TCPPassiveCloseNoTimeWaitReuseTest_NoRandomSave) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, - true /*accept_close*/, &listen_addr, &conn_bound_addr); - - FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - param.listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Now bind and connect new socket and verify that we can immediately rebind - // the address bound by the conn_fd as it never entered TIME_WAIT. - const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - param.connector.addr_len), - SyscallSucceeds()); - - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr)); - sockaddr_storage conn_addr = param.connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - param.connector.addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitTest_NoRandomSave) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, false /*reuse*/, - false /*accept_close*/, &listen_addr, &conn_bound_addr); - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT(bind(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - param.connector.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitReuseTest_NoRandomSave) { - auto const& param = GetParam(); - sockaddr_storage listen_addr, conn_bound_addr; - listen_addr = param.listener.addr; - setupTimeWaitClose(¶m.listener, ¶m.connector, true /*reuse*/, - false /*accept_close*/, &listen_addr, &conn_bound_addr); - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - param.connector.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - const uint16_t port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Set the userTimeout on the listening socket. - constexpr int kUserTimeout = 10; - ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kUserTimeout, sizeof(kUserTimeout)), - SyscallSucceeds()); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - // Verify that the accepted socket inherited the user timeout set on - // listening socket. - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(accepted.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kUserTimeout); -} - -TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - { - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - &addrlen), - SyscallSucceeds()); - } - - const uint16_t port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - - // TODO(b/157236388): Reenable Cooperative S/R once bug is fixed. - DisableSave ds; - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Trigger a RST by turning linger off and closing the socket. - struct linger opt = { - .l_onoff = 1, - .l_linger = 0, - }; - ASSERT_THAT( - setsockopt(conn_fd.get(), SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)), - SyscallSucceeds()); - ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds()); - - if (IsRunningOnGvisor()) { - // Gvisor packet procssing is asynchronous and can take a bit of time in - // some cases so we give it a bit of time to process the RST packet before - // calling accept. - // - // There is nothing to poll() on so we have no choice but to use a sleep - // here. - absl::SleepFor(absl::Milliseconds(100)); - } - - sockaddr_storage accept_addr; - socklen_t addrlen = sizeof(accept_addr); - - auto accept_fd = ASSERT_NO_ERRNO_AND_VALUE(Accept( - listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen)); - ASSERT_EQ(addrlen, listener.addr_len); - - // Wait for accept_fd to process the RST. - constexpr int kTimeout = 10000; - pollfd pfd = { - .fd = accept_fd.get(), - .events = POLLIN, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR); - - { - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT( - getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - // This should return ECONNRESET as the socket just received a RST packet - // from the peer. - ASSERT_EQ(optlen, sizeof(err)); - ASSERT_EQ(err, ECONNRESET); - } - { - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT( - getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - // This should return no error as the previous getsockopt call would have - // cleared the socket error. - ASSERT_EQ(optlen, sizeof(err)); - ASSERT_EQ(err, 0); - } - { - sockaddr_storage peer_addr; - socklen_t addrlen = sizeof(peer_addr); - // The socket is not connected anymore and should return ENOTCONN. - ASSERT_THAT(getpeername(accept_fd.get(), - reinterpret_cast<sockaddr*>(&peer_addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); - } -} - -// TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not -// saved. Enable S/R once issue is fixed. -TEST_P(SocketInetLoopbackTest, TCPDeferAccept_NoRandomSave) { - // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not - // saved. Enable S/R issue is fixed. - DisableSave ds; - - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - const uint16_t port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Set the TCP_DEFER_ACCEPT on the listening socket. - constexpr int kTCPDeferAccept = 3; - ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, - &kTCPDeferAccept, sizeof(kTCPDeferAccept)), - SyscallSucceeds()); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Set the listening socket to nonblock so that we can verify that there is no - // connection in queue despite the connect above succeeding since the peer has - // sent no data and TCP_DEFER_ACCEPT is set on the listening socket. Set the - // FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(listen_fd.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); - - ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Set FD back to blocking. - opts &= ~O_NONBLOCK; - ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); - - // Now write some data to the socket. - int data = 0; - ASSERT_THAT(RetryEINTR(write)(conn_fd.get(), &data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); - - // This should now cause the connection to complete and be delivered to the - // accept socket. - - // Accept the connection. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - - // Verify that the accepted socket returns the data written. - int get = -1; - ASSERT_THAT(RetryEINTR(recv)(accepted.get(), &get, sizeof(get), 0), - SyscallSucceedsWithValue(sizeof(get))); - - EXPECT_EQ(get, data); -} - -// TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not -// saved. Enable S/R once issue is fixed. -TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout_NoRandomSave) { - // TODO(gvisor.dev/issue/1688): Partially completed passive endpoints are not - // saved. Enable S/R once issue is fixed. - DisableSave ds; - - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - // Create the listening socket. - const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - - const uint16_t port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Set the TCP_DEFER_ACCEPT on the listening socket. - constexpr int kTCPDeferAccept = 3; - ASSERT_THAT(setsockopt(listen_fd.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, - &kTCPDeferAccept, sizeof(kTCPDeferAccept)), - SyscallSucceeds()); - - // Connect to the listening socket. - FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - // Set the listening socket to nonblock so that we can verify that there is no - // connection in queue despite the connect above succeeding since the peer has - // sent no data and TCP_DEFER_ACCEPT is set on the listening socket. Set the - // FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(listen_fd.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); - - // Verify that there is no acceptable connection before TCP_DEFER_ACCEPT - // timeout is hit. - absl::SleepFor(absl::Seconds(kTCPDeferAccept - 1)); - ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Set FD back to blocking. - opts &= ~O_NONBLOCK; - ASSERT_THAT(fcntl(listen_fd.get(), F_SETFL, opts), SyscallSucceeds()); - - // Now sleep for a little over the TCP_DEFER_ACCEPT duration. When the timeout - // is hit a SYN-ACK should be retransmitted by the listener as a last ditch - // attempt to complete the connection with or without data. - absl::SleepFor(absl::Seconds(2)); - - // Verify that we have a connection that can be accepted even though no - // data was written. - auto accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); -} - -INSTANTIATE_TEST_SUITE_P( - All, SocketInetLoopbackTest, - ::testing::Values( - // Listeners bound to IPv4 addresses refuse connections using IPv6 - // addresses. - TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()}, - TestParam{V4Any(), V4MappedAny()}, - TestParam{V4Any(), V4MappedLoopback()}, - TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, - TestParam{V4MappedAny(), V4Any()}, - TestParam{V4MappedAny(), V4Loopback()}, - TestParam{V4MappedAny(), V4MappedAny()}, - TestParam{V4MappedAny(), V4MappedLoopback()}, - TestParam{V4MappedLoopback(), V4Any()}, - TestParam{V4MappedLoopback(), V4Loopback()}, - TestParam{V4MappedLoopback(), V4MappedLoopback()}, - - // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()}, - TestParam{V6Any(), V4MappedAny()}, - TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()}, - TestParam{V6Any(), V6Loopback()}, - - // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 - // addresses. - TestParam{V6Loopback(), V6Any()}, - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); - -using SocketInetReusePortTest = ::testing::TestWithParam<TestParam>; - -// TODO(gvisor.dev/issue/940): Remove _NoRandomSave when portHint/stack.Seed is -// saved/restored. -TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread_NoRandomSave) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - sockaddr_storage listen_addr = listener.addr; - sockaddr_storage conn_addr = connector.addr; - constexpr int kThreadCount = 3; - constexpr int kConnectAttempts = 10000; - - // Create the listening socket. - FileDescriptor listener_fds[kThreadCount]; - for (int i = 0; i < kThreadCount; i++) { - listener_fds[i] = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - int fd = listener_fds[i].get(); - - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(fd, 40), SyscallSucceeds()); - - // On the first bind we need to determine which port was bound. - if (i != 0) { - continue; - } - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listener_fds[0].get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - } - - std::atomic<int> connects_received = ATOMIC_VAR_INIT(0); - std::unique_ptr<ScopedThread> listen_thread[kThreadCount]; - int accept_counts[kThreadCount] = {}; - // TODO(avagin): figure how to not disable S/R for the whole test. - // We need to take into account that this test executes a lot of system - // calls from many threads. - DisableSave ds; - - for (int i = 0; i < kThreadCount; i++) { - listen_thread[i] = absl::make_unique<ScopedThread>( - [&listener_fds, &accept_counts, i, &connects_received]() { - do { - auto fd = Accept(listener_fds[i].get(), nullptr, nullptr); - if (!fd.ok()) { - if (connects_received >= kConnectAttempts) { - // Another thread have shutdown our read side causing the - // accept to fail. - ASSERT_EQ(errno, EINVAL); - break; - } - ASSERT_NO_ERRNO(fd); - break; - } - // Receive some data from a socket to be sure that the connect() - // system call has been completed on another side. - // Do a short read and then close the socket to trigger a RST. This - // ensures that both ends of the connection are cleaned up and no - // goroutines hang around in TIME-WAIT. We do this so that this test - // does not timeout under gotsan runs where lots of goroutines can - // cause the test to use absurd amounts of memory. - // - // See: https://tools.ietf.org/html/rfc2525#page-50 section 2.17 - uint16_t data; - EXPECT_THAT( - RetryEINTR(recv)(fd.ValueOrDie().get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(sizeof(data))); - accept_counts[i]++; - } while (++connects_received < kConnectAttempts); - - // Shutdown all sockets to wake up other threads. - for (int j = 0; j < kThreadCount; j++) { - shutdown(listener_fds[j].get(), SHUT_RDWR); - } - }); - } - - ScopedThread connecting_thread([&connector, &conn_addr]() { - for (int32_t i = 0; i < kConnectAttempts; i++) { - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT( - RetryEINTR(connect)(fd.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceeds()); - - EXPECT_THAT(RetryEINTR(send)(fd.get(), &i, sizeof(i), 0), - SyscallSucceedsWithValue(sizeof(i))); - } - }); - - // Join threads to be sure that all connections have been counted - connecting_thread.Join(); - for (int i = 0; i < kThreadCount; i++) { - listen_thread[i]->Join(); - } - // Check that connections are distributed fairly between listening sockets - for (int i = 0; i < kThreadCount; i++) - EXPECT_THAT(accept_counts[i], - EquivalentWithin((kConnectAttempts / kThreadCount), 0.10)); -} - -TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread_NoRandomSave) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - sockaddr_storage listen_addr = listener.addr; - sockaddr_storage conn_addr = connector.addr; - constexpr int kThreadCount = 3; - - // Create the listening socket. - FileDescriptor listener_fds[kThreadCount]; - for (int i = 0; i < kThreadCount; i++) { - listener_fds[i] = - ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0)); - int fd = listener_fds[i].get(); - - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), - SyscallSucceeds()); - - // On the first bind we need to determine which port was bound. - if (i != 0) { - continue; - } - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listener_fds[0].get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - } - - constexpr int kConnectAttempts = 10000; - std::atomic<int> packets_received = ATOMIC_VAR_INIT(0); - std::unique_ptr<ScopedThread> receiver_thread[kThreadCount]; - int packets_per_socket[kThreadCount] = {}; - // TODO(avagin): figure how to not disable S/R for the whole test. - DisableSave ds; // Too expensive. - - for (int i = 0; i < kThreadCount; i++) { - receiver_thread[i] = absl::make_unique<ScopedThread>( - [&listener_fds, &packets_per_socket, i, &packets_received]() { - do { - struct sockaddr_storage addr = {}; - socklen_t addrlen = sizeof(addr); - int data; - - auto ret = RetryEINTR(recvfrom)( - listener_fds[i].get(), &data, sizeof(data), 0, - reinterpret_cast<struct sockaddr*>(&addr), &addrlen); - - if (packets_received < kConnectAttempts) { - ASSERT_THAT(ret, SyscallSucceedsWithValue(sizeof(data))); - } - - if (ret != sizeof(data)) { - // Another thread may have shutdown our read side causing the - // recvfrom to fail. - break; - } - - packets_received++; - packets_per_socket[i]++; - - // A response is required to synchronize with the main thread, - // otherwise the main thread can send more than can fit into receive - // queues. - EXPECT_THAT(RetryEINTR(sendto)( - listener_fds[i].get(), &data, sizeof(data), 0, - reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceedsWithValue(sizeof(data))); - } while (packets_received < kConnectAttempts); - - // Shutdown all sockets to wake up other threads. - for (int j = 0; j < kThreadCount; j++) - shutdown(listener_fds[j].get(), SHUT_RDWR); - }); - } - - ScopedThread main_thread([&connector, &conn_addr]() { - for (int i = 0; i < kConnectAttempts; i++) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0)); - EXPECT_THAT(RetryEINTR(sendto)(fd.get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceedsWithValue(sizeof(i))); - int data; - EXPECT_THAT(RetryEINTR(recv)(fd.get(), &data, sizeof(data), 0), - SyscallSucceedsWithValue(sizeof(data))); - } - }); - - main_thread.Join(); - - // Join threads to be sure that all connections have been counted - for (int i = 0; i < kThreadCount; i++) { - receiver_thread[i]->Join(); - } - // Check that packets are distributed fairly between listening sockets. - for (int i = 0; i < kThreadCount; i++) - EXPECT_THAT(packets_per_socket[i], - EquivalentWithin((kConnectAttempts / kThreadCount), 0.10)); -} - -TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort_NoRandomSave) { - auto const& param = GetParam(); - - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - sockaddr_storage listen_addr = listener.addr; - sockaddr_storage conn_addr = connector.addr; - constexpr int kThreadCount = 3; - - // TODO(b/141211329): endpointsByNic.seed has to be saved/restored. - const DisableSave ds141211329; - - // Create listening sockets. - FileDescriptor listener_fds[kThreadCount]; - for (int i = 0; i < kThreadCount; i++) { - listener_fds[i] = - ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0)); - int fd = listener_fds[i].get(); - - ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len), - SyscallSucceeds()); - - // On the first bind we need to determine which port was bound. - if (i != 0) { - continue; - } - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT( - getsockname(listener_fds[0].get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port)); - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - } - - constexpr int kConnectAttempts = 10; - FileDescriptor client_fds[kConnectAttempts]; - - // Do the first run without save/restore. - DisableSave ds; - for (int i = 0; i < kConnectAttempts; i++) { - client_fds[i] = - ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0)); - EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceedsWithValue(sizeof(i))); - } - ds.reset(); - - // Check that a mapping of client and server sockets has - // not been change after save/restore. - for (int i = 0; i < kConnectAttempts; i++) { - EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0, - reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len), - SyscallSucceedsWithValue(sizeof(i))); - } - - pollfd pollfds[kThreadCount]; - for (int i = 0; i < kThreadCount; i++) { - pollfds[i].fd = listener_fds[i].get(); - pollfds[i].events = POLLIN; - } - - std::map<uint16_t, int> portToFD; - - int received = 0; - while (received < kConnectAttempts * 2) { - ASSERT_THAT(poll(pollfds, kThreadCount, -1), - SyscallSucceedsWithValue(Gt(0))); - - for (int i = 0; i < kThreadCount; i++) { - if ((pollfds[i].revents & POLLIN) == 0) { - continue; - } - - received++; - - const int fd = pollfds[i].fd; - struct sockaddr_storage addr = {}; - socklen_t addrlen = sizeof(addr); - int data; - EXPECT_THAT(RetryEINTR(recvfrom)( - fd, &data, sizeof(data), 0, - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceedsWithValue(sizeof(data))); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(connector.family(), addr)); - auto prev_port = portToFD.find(port); - // Check that all packets from one client have been delivered to the - // same server socket. - if (prev_port == portToFD.end()) { - portToFD[port] = fd; - } else { - EXPECT_EQ(portToFD[port], fd); - } - } - } -} - -INSTANTIATE_TEST_SUITE_P( - All, SocketInetReusePortTest, - ::testing::Values( - // Listeners bound to IPv4 addresses refuse connections using IPv6 - // addresses. - TestParam{V4Any(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, - - // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V6Loopback()}, - - // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 - // addresses. - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); - -struct ProtocolTestParam { - std::string description; - int type; -}; - -std::string DescribeProtocolTestParam( - ::testing::TestParamInfo<ProtocolTestParam> const& info) { - return info.param.description; -} - -using SocketMultiProtocolInetLoopbackTest = - ::testing::TestWithParam<ProtocolTestParam>; - -TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v4 loopback on a dual stack socket. - TestAddress const& test_addr_dual = V4MappedLoopback(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that we can still bind the v6 loopback on the same port. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - int ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len); - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - ASSERT_THAT(ret, SyscallSucceeds()); - - // Verify that binding the v4 loopback with the same port on a v4 socket - // fails. - TestAddress const& test_addr_v4 = V4Loopback(); - sockaddr_storage addr_v4 = test_addr_v4.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); - const FileDescriptor fd_v4 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4), - test_addr_v4.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v4 any on a dual stack socket. - TestAddress const& test_addr_dual = V4MappedAny(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that we can still bind the v6 loopback on the same port. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - int ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len); - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - ASSERT_THAT(ret, SyscallSucceeds()); - - // Verify that binding the v4 loopback with the same port on a v4 socket - // fails. - TestAddress const& test_addr_v4 = V4Loopback(); - sockaddr_storage addr_v4 = test_addr_v4.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); - const FileDescriptor fd_v4 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4), - test_addr_v4.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) { - auto const& param = GetParam(); - - // Bind the v6 any on a dual stack socket. - TestAddress const& test_addr_dual = V6Any(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that binding the v6 loopback with the same port fails. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v6 socket - // fails. - TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); - sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port)); - const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_mapped.family(), param.type, 0)); - ASSERT_THAT( - bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped), - test_addr_v4_mapped.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v4 socket - // fails. - TestAddress const& test_addr_v4 = V4Loopback(); - sockaddr_storage addr_v4 = test_addr_v4.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); - const FileDescriptor fd_v4 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4), - test_addr_v4.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 any on the same port with a v4 socket - // fails. - TestAddress const& test_addr_v4_any = V4Any(); - sockaddr_storage addr_v4_any = test_addr_v4_any.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port)); - const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_any.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any), - test_addr_v4_any.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, - DualStackV6AnyReuseAddrDoesNotReserveV4Any) { - auto const& param = GetParam(); - - // Bind the v6 any on a dual stack socket. - TestAddress const& test_addr_dual = V6Any(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(setsockopt(fd_dual.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that binding the v4 any on the same port with a v4 socket succeeds. - TestAddress const& test_addr_v4_any = V4Any(); - sockaddr_storage addr_v4_any = test_addr_v4_any.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port)); - const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_any.family(), param.type, 0)); - ASSERT_THAT(setsockopt(fd_v4_any.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any), - test_addr_v4_any.addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, - DualStackV6AnyReuseAddrListenReservesV4Any) { - auto const& param = GetParam(); - - // Only TCP sockets are supported. - SKIP_IF((param.type & SOCK_STREAM) == 0); - - // Bind the v6 any on a dual stack socket. - TestAddress const& test_addr_dual = V6Any(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(setsockopt(fd_dual.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - ASSERT_THAT(listen(fd_dual.get(), 5), SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that binding the v4 any on the same port with a v4 socket succeeds. - TestAddress const& test_addr_v4_any = V4Any(); - sockaddr_storage addr_v4_any = test_addr_v4_any.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port)); - const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_any.family(), param.type, 0)); - ASSERT_THAT(setsockopt(fd_v4_any.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any), - test_addr_v4_any.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, - DualStackV6AnyWithListenReservesEverything) { - auto const& param = GetParam(); - - // Only TCP sockets are supported. - SKIP_IF((param.type & SOCK_STREAM) == 0); - - // Bind the v6 any on a dual stack socket. - TestAddress const& test_addr_dual = V6Any(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0)); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - ASSERT_THAT(listen(fd_dual.get(), 5), SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that binding the v6 loopback with the same port fails. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v6 socket - // fails. - TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); - sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port)); - const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_mapped.family(), param.type, 0)); - ASSERT_THAT( - bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped), - test_addr_v4_mapped.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v4 socket - // fails. - TestAddress const& test_addr_v4 = V4Loopback(); - sockaddr_storage addr_v4 = test_addr_v4.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port)); - const FileDescriptor fd_v4 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4), - test_addr_v4.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 any on the same port with a v4 socket - // fails. - TestAddress const& test_addr_v4_any = V4Any(); - sockaddr_storage addr_v4_any = test_addr_v4_any.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port)); - const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_any.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any), - test_addr_v4_any.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v6 any on a v6-only socket. - TestAddress const& test_addr_dual = V6Any(); - sockaddr_storage addr_dual = test_addr_dual.addr; - const FileDescriptor fd_dual = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_dual.family(), param.type, 0)); - EXPECT_THAT(setsockopt(fd_dual.get(), IPPROTO_IPV6, IPV6_V6ONLY, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual), - test_addr_dual.addr_len), - SyscallSucceeds()); - - // Get the port that we bound. - socklen_t addrlen = test_addr_dual.addr_len; - ASSERT_THAT(getsockname(fd_dual.get(), - reinterpret_cast<sockaddr*>(&addr_dual), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual)); - - // Verify that binding the v6 loopback with the same port fails. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that we can still bind the v4 loopback on the same port. - TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); - sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port)); - const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_mapped.family(), param.type, 0)); - int ret = - bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped), - test_addr_v4_mapped.addr_len); - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - ASSERT_THAT(ret, SyscallSucceeds()); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v6 loopback on a dual stack socket. - TestAddress const& test_addr = V6Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v6 loopback with the same port fails. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); - const FileDescriptor fd_v6 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that we can still bind the v4 loopback on the same port. - TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); - sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, - ephemeral_port)); - const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_mapped.family(), param.type, 0)); - int ret = - bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped), - test_addr_v4_mapped.addr_len); - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - EXPECT_THAT(ret, SyscallSucceeds()); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) { - auto const& param = GetParam(); - - // Bind the v6 loopback on a dual stack socket. - TestAddress const& test_addr = V6Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v4 loopback on a dual stack socket. - TestAddress const& test_addr = V4MappedLoopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v4 socket - // fails. - TestAddress const& test_addr_v4 = V4Loopback(); - sockaddr_storage addr_v4 = test_addr_v4.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v4.family(), &addr_v4, ephemeral_port)); - const FileDescriptor fd_v4 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0)); - EXPECT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4), - test_addr_v4.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v6 any on the same port with a dual-stack socket - // fails. - TestAddress const& test_addr_v6_any = V6Any(); - sockaddr_storage addr_v6_any = test_addr_v6_any.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port)); - const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6_any.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any), - test_addr_v6_any.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we - // tend to run out of ephemeral ports? Regardless, binding the v6 loopback - // seems pretty reliable. Only try to bind the v6-only any on UDP and - // gVisor. - - int ret = -1; - - if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) { - // Verify that we can still bind the v6 loopback on the same port. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); - const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6.family(), param.type, 0)); - ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len); - } else { - // Verify that we can still bind the v6 any on the same port with a - // v6-only socket. - const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6_any.family(), param.type, 0)); - EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ret = - bind(fd_v6_only_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any), - test_addr_v6_any.addr_len); - } - - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - EXPECT_THAT(ret, SyscallSucceeds()); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, - V4MappedEphemeralPortReservedResueAddr) { - auto const& param = GetParam(); - - // Bind the v4 loopback on a dual stack socket. - TestAddress const& test_addr = V4MappedLoopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) { - auto const& param = GetParam(); - - for (int i = 0; true; i++) { - // Bind the v4 loopback on a v4 socket. - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v4 loopback on the same port with a v6 socket - // fails. - TestAddress const& test_addr_v4_mapped = V4MappedLoopback(); - sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr; - ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, - ephemeral_port)); - const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v4_mapped.family(), param.type, 0)); - EXPECT_THAT( - bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped), - test_addr_v4_mapped.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // Verify that binding the v6 any on the same port with a dual-stack socket - // fails. - TestAddress const& test_addr_v6_any = V6Any(); - sockaddr_storage addr_v6_any = test_addr_v6_any.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v6_any.family(), &addr_v6_any, ephemeral_port)); - const FileDescriptor fd_v6_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6_any.family(), param.type, 0)); - ASSERT_THAT(bind(fd_v6_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any), - test_addr_v6_any.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); - - // For some reason, binding the TCP v6-only any is flaky on Linux. Maybe we - // tend to run out of ephemeral ports? Regardless, binding the v6 loopback - // seems pretty reliable. Only try to bind the v6-only any on UDP and - // gVisor. - - int ret = -1; - - if (!IsRunningOnGvisor() && param.type == SOCK_STREAM) { - // Verify that we can still bind the v6 loopback on the same port. - TestAddress const& test_addr_v6 = V6Loopback(); - sockaddr_storage addr_v6 = test_addr_v6.addr; - ASSERT_NO_ERRNO( - SetAddrPort(test_addr_v6.family(), &addr_v6, ephemeral_port)); - const FileDescriptor fd_v6 = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6.family(), param.type, 0)); - ret = bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6), - test_addr_v6.addr_len); - } else { - // Verify that we can still bind the v6 any on the same port with a - // v6-only socket. - const FileDescriptor fd_v6_only_any = ASSERT_NO_ERRNO_AND_VALUE( - Socket(test_addr_v6_any.family(), param.type, 0)); - EXPECT_THAT(setsockopt(fd_v6_only_any.get(), IPPROTO_IPV6, IPV6_V6ONLY, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ret = - bind(fd_v6_only_any.get(), reinterpret_cast<sockaddr*>(&addr_v6_any), - test_addr_v6_any.addr_len); - } - - if (ret == -1 && errno == EADDRINUSE) { - // Port may have been in use. - ASSERT_LT(i, 100); // Give up after 100 tries. - continue; - } - EXPECT_THAT(ret, SyscallSucceeds()); - - // No need to try again. - break; - } -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReservedReuseAddr) { - auto const& param = GetParam(); - - // Bind the v4 loopback on a v4 socket. - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - const FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - - // Listen iff TCP. - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds()); - } - - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Connect to bind an ephemeral port. - const FileDescriptor connected_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), - reinterpret_cast<sockaddr*>(&bound_addr), - bound_addr_len), - SyscallSucceeds()); - - // Get the ephemeral port. - sockaddr_storage connected_addr = {}; - socklen_t connected_addr_len = sizeof(connected_addr); - ASSERT_THAT(getsockname(connected_fd.get(), - reinterpret_cast<sockaddr*>(&connected_addr), - &connected_addr_len), - SyscallSucceeds()); - uint16_t const ephemeral_port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr)); - - // Verify that we actually got an ephemeral port. - ASSERT_NE(ephemeral_port, 0); - - // Verify that the ephemeral port is not reserved. - const FileDescriptor checking_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - EXPECT_THAT( - bind(checking_fd.get(), reinterpret_cast<sockaddr*>(&connected_addr), - connected_addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, - MultipleBindsAllowedNoListeningReuseAddr) { - const auto& param = GetParam(); - // UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP - // this is only permitted if there is no other listening socket. - SKIP_IF(param.type != SOCK_STREAM); - // Bind the v4 loopback on a v4 socket. - const TestAddress& test_addr = V4Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - - // Now create a socket and bind it to the same port, this should - // succeed since there is no listening socket for the same port. - FileDescriptor second_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(second_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(second_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len), - SyscallSucceeds()); -} - -TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) { - auto const& param = GetParam(); - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage addr = test_addr.addr; - - for (int i = 0; i < 2; i++) { - const int portreuse1 = i % 2; - auto s1 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - int fd1 = s1.get(); - socklen_t addrlen = test_addr.addr_len; - - EXPECT_THAT( - setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, &portreuse1, sizeof(int)), - SyscallSucceeds()); - - ASSERT_THAT(bind(fd1, reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(getsockname(fd1, reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - if (param.type == SOCK_STREAM) { - ASSERT_THAT(listen(fd1, 1), SyscallSucceeds()); - } - - // j is less than 4 to check that the port reuse logic works correctly after - // closing bound sockets. - for (int j = 0; j < 4; j++) { - const int portreuse2 = j % 2; - auto s2 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - int fd2 = s2.get(); - - EXPECT_THAT( - setsockopt(fd2, SOL_SOCKET, SO_REUSEPORT, &portreuse2, sizeof(int)), - SyscallSucceeds()); - - std::cout << portreuse1 << " " << portreuse2 << std::endl; - int ret = bind(fd2, reinterpret_cast<sockaddr*>(&addr), addrlen); - - // Verify that two sockets can be bound to the same port only if - // SO_REUSEPORT is set for both of them. - if (!portreuse1 || !portreuse2) { - ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRINUSE)); - } else { - ASSERT_THAT(ret, SyscallSucceeds()); - } - } - } -} - -// Check that when a socket was bound to an address with REUSEPORT and then -// closed, we can bind a different socket to the same address without needing -// REUSEPORT. -TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) { - auto const& param = GetParam(); - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage addr = test_addr.addr; - - auto s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - int fd = s.get(); - socklen_t addrlen = test_addr.addr_len; - int portreuse = 1; - ASSERT_THAT( - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - ASSERT_THAT(getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - ASSERT_EQ(addrlen, test_addr.addr_len); - - s.reset(); - - // Open a new socket and bind to the same address, but w/o REUSEPORT. - s = ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - fd = s.get(); - portreuse = 0; - ASSERT_THAT( - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &portreuse, sizeof(portreuse)), - SyscallSucceeds()); - ASSERT_THAT(bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceeds()); -} - -INSTANTIATE_TEST_SUITE_P( - AllFamilies, SocketMultiProtocolInetLoopbackTest, - ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, - ProtocolTestParam{"UDP", SOCK_DGRAM}), - DescribeProtocolTestParam); - -} // namespace - -// Check that loopback receives connections from any address in the range: -// 127.0.0.1 to 127.254.255.255. This behavior is exclusive to IPv4. -TEST_F(SocketInetLoopbackTest, LoopbackAddressRangeConnect) { - TestAddress const& listener = V4Any(); - - in_addr_t addresses[] = { - INADDR_LOOPBACK, - INADDR_LOOPBACK + 1, // 127.0.0.2 - (in_addr_t)0x7f000101, // 127.0.1.1 - (in_addr_t)0x7f010101, // 127.1.1.1 - (in_addr_t)0x7ffeffff, // 127.254.255.255 - }; - for (const auto& address : addresses) { - TestAddress connector("V4Loopback"); - connector.addr.ss_family = AF_INET; - connector.addr_len = sizeof(sockaddr_in); - reinterpret_cast<sockaddr_in*>(&connector.addr)->sin_addr.s_addr = - htonl(address); - - tcpSimpleConnectTest(listener, connector, true); - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc deleted file mode 100644 index 1a0b53394..000000000 --- a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <string.h> - -#include <iostream> -#include <memory> -#include <string> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::Gt; - -PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) { - switch (family) { - case AF_INET: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in const*>(&addr)->sin_port); - case AF_INET6: - return static_cast<uint16_t>( - reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { - switch (family) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(addr)->sin_port = port; - return NoError(); - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port; - return NoError(); - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } -} - -struct TestParam { - TestAddress listener; - TestAddress connector; -}; - -std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) { - return absl::StrCat("Listen", info.param.listener.description, "_Connect", - info.param.connector.description); -} - -using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>; - -// This test verifies that connect returns EADDRNOTAVAIL if all local ephemeral -// ports are already in use for a given destination ip/port. -// -// We disable S/R because this test creates a large number of sockets. -// -// FIXME(b/162475855): This test is failing reliably. -TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion_NoRandomSave) { - auto const& param = GetParam(); - TestAddress const& listener = param.listener; - TestAddress const& connector = param.connector; - - constexpr int kBacklog = 10; - constexpr int kClients = 65536; - - // Create the listening socket. - auto listen_fd = ASSERT_NO_ERRNO_AND_VALUE( - Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP)); - sockaddr_storage listen_addr = listener.addr; - ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr), - listener.addr_len), - SyscallSucceeds()); - ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds()); - - // Get the port bound by the listening socket. - socklen_t addrlen = listener.addr_len; - ASSERT_THAT(getsockname(listen_fd.get(), - reinterpret_cast<sockaddr*>(&listen_addr), &addrlen), - SyscallSucceeds()); - uint16_t const port = - ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr)); - - // Disable cooperative S/R as we are making too many syscalls. - DisableSave ds; - - // Now we keep opening connections till we run out of local ephemeral ports. - // and assert the error we get back. - sockaddr_storage conn_addr = connector.addr; - ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port)); - std::vector<FileDescriptor> clients; - std::vector<FileDescriptor> servers; - - for (int i = 0; i < kClients; i++) { - FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE( - Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP)); - int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr), - connector.addr_len); - if (ret == 0) { - clients.push_back(std::move(client)); - FileDescriptor server = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr)); - servers.push_back(std::move(server)); - continue; - } - ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRNOTAVAIL)); - break; - } -} - -INSTANTIATE_TEST_SUITE_P( - All, SocketInetLoopbackTest, - ::testing::Values( - // Listeners bound to IPv4 addresses refuse connections using IPv6 - // addresses. - TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()}, - TestParam{V4Any(), V4MappedAny()}, - TestParam{V4Any(), V4MappedLoopback()}, - TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()}, - TestParam{V4Loopback(), V4MappedLoopback()}, - TestParam{V4MappedAny(), V4Any()}, - TestParam{V4MappedAny(), V4Loopback()}, - TestParam{V4MappedAny(), V4MappedAny()}, - TestParam{V4MappedAny(), V4MappedLoopback()}, - TestParam{V4MappedLoopback(), V4Any()}, - TestParam{V4MappedLoopback(), V4Loopback()}, - TestParam{V4MappedLoopback(), V4MappedLoopback()}, - - // Listeners bound to IN6ADDR_ANY accept all connections. - TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()}, - TestParam{V6Any(), V4MappedAny()}, - TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()}, - TestParam{V6Any(), V6Loopback()}, - - // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4 - // addresses. - TestParam{V6Loopback(), V6Any()}, - TestParam{V6Loopback(), V6Loopback()}), - DescribeTestParam); - -struct ProtocolTestParam { - std::string description; - int type; -}; - -std::string DescribeProtocolTestParam( - ::testing::TestParamInfo<ProtocolTestParam> const& info) { - return info.param.description; -} - -using SocketMultiProtocolInetLoopbackTest = - ::testing::TestWithParam<ProtocolTestParam>; - -TEST_P(SocketMultiProtocolInetLoopbackTest, - BindAvoidsListeningPortsReuseAddr_NoRandomSave) { - const auto& param = GetParam(); - // UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP - // this is only permitted if there is no other listening socket. - SKIP_IF(param.type != SOCK_STREAM); - - DisableSave ds; // Too many syscalls. - - // A map of port to file descriptor binding the port. - std::map<uint16_t, FileDescriptor> listen_sockets; - - // Exhaust all ephemeral ports. - while (true) { - // Bind the v4 loopback on a v4 socket. - TestAddress const& test_addr = V4Loopback(); - sockaddr_storage bound_addr = test_addr.addr; - FileDescriptor bound_fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0)); - - ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int ret = bind(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - test_addr.addr_len); - if (ret != 0) { - ASSERT_EQ(errno, EADDRINUSE); - break; - } - // Get the port that we bound. - socklen_t bound_addr_len = test_addr.addr_len; - ASSERT_THAT( - getsockname(bound_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr), - &bound_addr_len), - SyscallSucceeds()); - uint16_t port = reinterpret_cast<sockaddr_in*>(&bound_addr)->sin_port; - - // Newly bound port should not already be in use by a listening socket. - ASSERT_EQ(listen_sockets.find(port), listen_sockets.end()); - auto fd = bound_fd.get(); - listen_sockets.insert(std::make_pair(port, std::move(bound_fd))); - ASSERT_THAT(listen(fd, SOMAXCONN), SyscallSucceeds()); - } -} - -INSTANTIATE_TEST_SUITE_P( - AllFamilies, SocketMultiProtocolInetLoopbackTest, - ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM}, - ProtocolTestParam{"UDP", SOCK_DGRAM}), - DescribeProtocolTestParam); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_loopback_blocking.cc b/test/syscalls/linux/socket_ip_loopback_blocking.cc deleted file mode 100644 index fda252dd7..000000000 --- a/test/syscalls/linux/socket_ip_loopback_blocking.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>( - std::vector<SocketPairKind>{ - IPv6UDPBidirectionalBindSocketPair(0), - IPv4UDPBidirectionalBindSocketPair(0), - }, - ApplyVecToVec<SocketPairKind>( - std::vector<Middleware>{ - NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)}, - std::vector<SocketPairKind>{ - IPv6TCPAcceptBindSocketPair(0), - IPv4TCPAcceptBindSocketPair(0), - })); -} - -INSTANTIATE_TEST_SUITE_P( - BlockingIPSockets, BlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc deleted file mode 100644 index f10f55b27..000000000 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ /dev/null @@ -1,1308 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_ip_tcp_generic.h" - -#include <fcntl.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <poll.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -using ::testing::AnyOf; -using ::testing::Eq; - -TEST_P(TCPSocketPairTest, TcpInfoSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct tcp_info opt = {}; - socklen_t optLen = sizeof(opt); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), - SyscallSucceeds()); -} - -TEST_P(TCPSocketPairTest, ShortTcpInfoSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct tcp_info opt = {}; - socklen_t optLen = 1; - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), - SyscallSucceeds()); -} - -TEST_P(TCPSocketPairTest, ZeroTcpInfoSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct tcp_info opt = {}; - socklen_t optLen = 0; - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), - SyscallSucceeds()); -} - -// Copied from include/net/tcp.h. -constexpr int TCP_CA_OPEN = 0; - -TEST_P(TCPSocketPairTest, CheckTcpInfoFields) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until second_fd sees the data and then recv it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0}; - constexpr int kPollTimeoutMs = 2000; // Wait up to 2 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - struct tcp_info opt = {}; - socklen_t optLen = sizeof(opt); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), - SyscallSucceeds()); - ASSERT_EQ(optLen, sizeof(opt)); - - // Validates the received tcp_info fields. - EXPECT_EQ(opt.tcpi_ca_state, TCP_CA_OPEN); - EXPECT_GT(opt.tcpi_snd_cwnd, 0); - EXPECT_GT(opt.tcpi_rto, 0); -} - -// This test validates that an RST is sent instead of a FIN when data is -// unread on calls to close(2). -TEST_P(TCPSocketPairTest, RSTSentOnCloseWithUnreadData) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until t_ sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now close the connected without reading the data. - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - - // Wait for the other end to receive the RST (up to 20 seconds). - struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // A shutdown with unread data will cause a RST to be sent instead - // of a FIN, per RFC 2525 section 2.17; this is also what Linux does. - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); -} - -// This test will validate that a RST will cause POLLHUP to trigger. -TEST_P(TCPSocketPairTest, RSTCausesPollHUP) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until second sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(poll_fd.revents & POLLIN, POLLIN); - - // Confirm we at least have one unread byte. - int bytes_available = 0; - ASSERT_THAT( - RetryEINTR(ioctl)(sockets->second_fd(), FIONREAD, &bytes_available), - SyscallSucceeds()); - EXPECT_GT(bytes_available, 0); - - // Now close the connected socket without reading the data from the second, - // this will cause a RST and we should see that with POLLHUP. - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - - // Wait for the other end to receive the RST (up to 20 seconds). - struct pollfd poll_fd3 = {sockets->first_fd(), POLLHUP, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd3, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - ASSERT_NE(poll_fd3.revents & POLLHUP, 0); -} - -// This test validates that even if a RST is sent the other end will not -// get an ECONNRESET until it's read all data. -TEST_P(TCPSocketPairTest, RSTSentOnCloseWithUnreadDataAllowsReadBuffered) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(RetryEINTR(write)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until second sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0}; - constexpr int kPollTimeoutMs = 30000; // Wait up to 30 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Wait until first sees the data on its side but don't read it. - struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now close the connected socket without reading the data from the second. - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - - // Wait for the other end to receive the RST (up to 30 seconds). - struct pollfd poll_fd3 = {sockets->first_fd(), POLLHUP, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd3, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Since we also have data buffered we should be able to read it before - // the syscall will fail with ECONNRESET. - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // A shutdown with unread data will cause a RST to be sent instead - // of a FIN, per RFC 2525 section 2.17; this is also what Linux does. - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); -} - -// This test will verify that a clean shutdown (FIN) is preformed when there -// is unread data but only the write side is closed. -TEST_P(TCPSocketPairTest, FINSentOnShutdownWrWithUnreadData) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until t_ sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now shutdown the write end leaving the read end open. - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_WR), SyscallSucceeds()); - - // Wait for the other end to receive the FIN (up to 20 seconds). - struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Since we didn't shutdown the read end this will be a clean close. - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); -} - -// This test will verify that when data is received by a socket, even if it's -// not read SHUT_RD will not cause any packets to be generated. -TEST_P(TCPSocketPairTest, ShutdownRdShouldCauseNoPacketsWithUnreadData) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until t_ sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now shutdown the read end, this will generate no packets to the other end. - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds()); - - // We should not receive any events on the other side of the socket. - struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollNoResponseTimeoutMs = 3000; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollNoResponseTimeoutMs), - SyscallSucceedsWithValue(0)); // Timeout. -} - -// This test will verify that a socket which has unread data will still allow -// the data to be read after shutting down the read side, and once there is no -// unread data left, then read will return an EOF. -TEST_P(TCPSocketPairTest, ShutdownRdAllowsReadOfReceivedDataBeforeEOF) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until t_ sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now shutdown the read end. - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds()); - - // Even though we did a SHUT_RD on the read end we can still read the data. - ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // After reading all of the data, reading the closed read end returns EOF. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); -} - -// This test verifies that a shutdown(wr) by the server after sending -// data allows the client to still read() the queued data and a client -// close after sending response allows server to read the incoming -// response. -TEST_P(TCPSocketPairTest, ShutdownWrServerClientClose) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[10] = {}; - ScopedThread t([&]() { - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(close(sockets->release_first_fd()), - SyscallSucceedsWithValue(0)); - }); - ASSERT_THAT(RetryEINTR(write)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(RetryEINTR(shutdown)(sockets->second_fd(), SHUT_WR), - SyscallSucceedsWithValue(0)); - t.Join(); - - ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(TCPSocketPairTest, ClosedReadNonBlockingSocket) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Set the read end to O_NONBLOCK. - int opts = 0; - ASSERT_THAT(opts = fcntl(sockets->second_fd(), F_GETFL), SyscallSucceeds()); - ASSERT_THAT(fcntl(sockets->second_fd(), F_SETFL, opts | O_NONBLOCK), - SyscallSucceeds()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until second_fd sees the data and then recv it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0}; - constexpr int kPollTimeoutMs = 2000; // Wait up to 2 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - // Now shutdown the write end leaving the read end open. - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - - // Wait for close notification and recv again. - struct pollfd poll_fd2 = {sockets->second_fd(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(0)); -} - -TEST_P(TCPSocketPairTest, - ShutdownRdUnreadDataShouldCauseNoPacketsUnlessClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // Wait until t_ sees the data on its side but don't read it. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data. - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - // Now shutdown the read end, this will generate no packets to the other end. - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds()); - - // We should not receive any events on the other side of the socket. - struct pollfd poll_fd2 = {sockets->first_fd(), POLLIN | POLLHUP, 0}; - constexpr int kPollNoResponseTimeoutMs = 3000; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollNoResponseTimeoutMs), - SyscallSucceedsWithValue(0)); // Timeout. - - // Now since we've fully closed the connection it will generate a RST. - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); // The other end has closed. - - // A shutdown with unread data will cause a RST to be sent instead - // of a FIN, per RFC 2525 section 2.17; this is also what Linux does. - ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); -} - -TEST_P(TCPSocketPairTest, TCPCorkDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(TCPSocketPairTest, SetTCPCork) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(TCPSocketPairTest, TCPCork) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - constexpr char kData[] = "abc"; - ASSERT_THAT(WriteFd(sockets->first_fd(), kData, sizeof(kData)), - SyscallSucceedsWithValue(sizeof(kData))); - - ASSERT_NO_FATAL_FAILURE(RecvNoData(sockets->second_fd())); - - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CORK, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Create a receive buffer larger than kData. - char buf[(sizeof(kData) + 1) * 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kData))); - EXPECT_EQ(absl::string_view(kData, sizeof(kData)), - absl::string_view(buf, sizeof(kData))); -} - -TEST_P(TCPSocketPairTest, TCPQuickAckDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(TCPSocketPairTest, SetTCPQuickAck) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_QUICKACK, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(TCPSocketPairTest, SoKeepaliveDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(TCPSocketPairTest, SetSoKeepalive) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_KEEPALIVE, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(TCPSocketPairTest, TCPKeepidleDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 2 * 60 * 60); // 2 hours. -} - -TEST_P(TCPSocketPairTest, TCPKeepintvlDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 75); // 75 seconds. -} - -TEST_P(TCPSocketPairTest, SetTCPKeepidleZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &kZero, - sizeof(kZero)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepintvlZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, - &kZero, sizeof(kZero)), - SyscallFailsWithErrno(EINVAL)); -} - -// Copied from include/net/tcp.h. -constexpr int MAX_TCP_KEEPIDLE = 32767; -constexpr int MAX_TCP_KEEPINTVL = 32767; -constexpr int MAX_TCP_KEEPCNT = 127; - -TEST_P(TCPSocketPairTest, SetTCPKeepidleAboveMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kAboveMax = MAX_TCP_KEEPIDLE + 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, - &kAboveMax, sizeof(kAboveMax)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepintvlAboveMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kAboveMax = MAX_TCP_KEEPINTVL + 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, - &kAboveMax, sizeof(kAboveMax)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepidleToMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, - &MAX_TCP_KEEPIDLE, sizeof(MAX_TCP_KEEPIDLE)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPIDLE, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, MAX_TCP_KEEPIDLE); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepintvlToMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, - &MAX_TCP_KEEPINTVL, sizeof(MAX_TCP_KEEPINTVL)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPINTVL, &get, - &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, MAX_TCP_KEEPINTVL); -} - -TEST_P(TCPSocketPairTest, TCPKeepcountDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 9); // 9 keepalive probes. -} - -TEST_P(TCPSocketPairTest, SetTCPKeepcountZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &kZero, - sizeof(kZero)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepcountAboveMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kAboveMax = MAX_TCP_KEEPCNT + 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, - &kAboveMax, sizeof(kAboveMax)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepcountToMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, - &MAX_TCP_KEEPCNT, sizeof(MAX_TCP_KEEPCNT)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, MAX_TCP_KEEPCNT); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepcountToOne) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int keepaliveCount = 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, - &keepaliveCount, sizeof(keepaliveCount)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, keepaliveCount); -} - -TEST_P(TCPSocketPairTest, SetTCPKeepcountToNegative) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int keepaliveCount = -5; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, - &keepaliveCount, sizeof(keepaliveCount)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(TCPSocketPairTest, SetOOBInline) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(TCPSocketPairTest, MsgTruncMsgPeek) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // Read half of the data with MSG_TRUNC | MSG_PEEK. This way there will still - // be some data left to read in the next step even if the data gets consumed. - char received_data1[sizeof(sent_data) / 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data1, - sizeof(received_data1), MSG_TRUNC | MSG_PEEK), - SyscallSucceedsWithValue(sizeof(received_data1))); - - // Check that we didn't get anything. - char zeros[sizeof(received_data1)] = {}; - EXPECT_EQ(0, memcmp(zeros, received_data1, sizeof(received_data1))); - - // Check that all of the data is still there. - char received_data2[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data2, - sizeof(received_data2), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - EXPECT_EQ(0, memcmp(received_data2, sent_data, sizeof(sent_data))); -} - -TEST_P(TCPSocketPairTest, SetCongestionControlSucceedsForSupported) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - // Netstack only supports reno & cubic so we only test these two values here. - { - const char kSetCC[kTcpCaNameMax] = "reno"; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &kSetCC, strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax]; - memset(got_cc, '1', sizeof(got_cc)); - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC))); - } - { - const char kSetCC[kTcpCaNameMax] = "cubic"; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &kSetCC, strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax]; - memset(got_cc, '1', sizeof(got_cc)); - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC))); - } -} - -TEST_P(TCPSocketPairTest, SetGetTCPCongestionShortReadBuffer) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - { - // Verify that getsockopt/setsockopt work with buffers smaller than - // kTcpCaNameMax. - const char kSetCC[] = "cubic"; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &kSetCC, strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[sizeof(kSetCC)]; - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(got_cc))); - } -} - -TEST_P(TCPSocketPairTest, SetGetTCPCongestionLargeReadBuffer) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - { - // Verify that getsockopt works with buffers larger than - // kTcpCaNameMax. - const char kSetCC[] = "cubic"; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &kSetCC, strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax + 5]; - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - // Linux copies the minimum of kTcpCaNameMax or the length of the passed in - // buffer and sets optlen to the number of bytes actually copied - // irrespective of the actual length of the congestion control name. - EXPECT_EQ(kTcpCaNameMax, optlen); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC))); - } -} - -TEST_P(TCPSocketPairTest, SetCongestionControlFailsForUnsupported) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char old_cc[kTcpCaNameMax]; - socklen_t optlen = sizeof(old_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &old_cc, &optlen), - SyscallSucceedsWithValue(0)); - - const char kSetCC[] = "invalid_ca_cc"; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &kSetCC, strlen(kSetCC)), - SyscallFailsWithErrno(ENOENT)); - - char got_cc[kTcpCaNameMax]; - optlen = sizeof(got_cc); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_CONGESTION, - &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(0, memcmp(got_cc, old_cc, sizeof(old_cc))); -} - -// Linux and Netstack both default to a 60s TCP_LINGER2 timeout. -constexpr int kDefaultTCPLingerTimeout = 60; -// On Linux, the maximum linger2 timeout was changed from 60sec to 120sec. -constexpr int kMaxTCPLingerTimeout = 120; -constexpr int kOldMaxTCPLingerTimeout = 60; - -TEST_P(TCPSocketPairTest, TCPLingerTimeoutDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kDefaultTCPLingerTimeout); -} - -TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutLessThanZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kNegative = -1234; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, - &kNegative, sizeof(kNegative)), - SyscallSucceedsWithValue(0)); - int get = INT_MAX; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, -1); -} - -TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &kZero, - sizeof(kZero)), - SyscallSucceedsWithValue(0)); - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_THAT(get, - AnyOf(Eq(kMaxTCPLingerTimeout), Eq(kOldMaxTCPLingerTimeout))); -} - -TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Values above the net.ipv4.tcp_fin_timeout are capped to tcp_fin_timeout - // on linux (defaults to 60 seconds on linux). - constexpr int kAboveDefault = kMaxTCPLingerTimeout + 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, - &kAboveDefault, sizeof(kAboveDefault)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - if (IsRunningOnGvisor()) { - EXPECT_EQ(get, kMaxTCPLingerTimeout); - } else { - EXPECT_THAT(get, - AnyOf(Eq(kMaxTCPLingerTimeout), Eq(kOldMaxTCPLingerTimeout))); - } -} - -TEST_P(TCPSocketPairTest, SetTCPLingerTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Values above the net.ipv4.tcp_fin_timeout are capped to tcp_fin_timeout - // on linux (defaults to 60 seconds on linux). - constexpr int kTCPLingerTimeout = kDefaultTCPLingerTimeout - 1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, - &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT( - getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kTCPLingerTimeout); -} - -TEST_P(TCPSocketPairTest, TestTCPCloseWithData) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ScopedThread t([&]() { - // Close one end to trigger sending of a FIN. - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_WR), SyscallSucceeds()); - char buf[3]; - ASSERT_THAT(read(sockets->second_fd(), buf, 3), - SyscallSucceedsWithValue(3)); - absl::SleepFor(absl::Milliseconds(50)); - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - }); - - absl::SleepFor(absl::Milliseconds(50)); - // Send some data then close. - constexpr char kStr[] = "abc"; - ASSERT_THAT(write(sockets->first_fd(), kStr, 3), SyscallSucceedsWithValue(3)); - t.Join(); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); -} - -TEST_P(TCPSocketPairTest, TCPUserTimeoutDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 0); // 0 ms (disabled). -} - -TEST_P(TCPSocketPairTest, SetTCPUserTimeoutZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kZero = 0; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kZero, sizeof(kZero)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 0); // 0 ms (disabled). -} - -TEST_P(TCPSocketPairTest, SetTCPUserTimeoutBelowZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kNeg = -10; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kNeg, sizeof(kNeg)), - SyscallFailsWithErrno(EINVAL)); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 0); // 0 ms (disabled). -} - -TEST_P(TCPSocketPairTest, SetTCPUserTimeoutAboveZero) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kAbove = 10; - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kAbove, sizeof(kAbove)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kAbove); -} - -#ifdef __linux__ -TEST_P(TCPSocketPairTest, SpliceFromPipe) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_THAT( - splice(rfd.get(), nullptr, sockets->first_fd(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(buf.size())); - - std::vector<char> rbuf(buf.size()); - ASSERT_THAT(read(sockets->second_fd(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(buf.size())); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST_P(TCPSocketPairTest, SpliceToPipe) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(sockets->first_fd(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - shutdown(sockets->first_fd(), SHUT_WR); - EXPECT_THAT( - splice(sockets->second_fd(), nullptr, wfd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(buf.size())); - - std::vector<char> rbuf(buf.size()); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(buf.size())); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -#include <sys/sendfile.h> - -TEST_P(TCPSocketPairTest, SendfileFromRegularFileSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - // Fill with some random data. - std::vector<char> buf(kPageSize / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(pwrite(in_fd.get(), buf.data(), buf.size(), 0), - SyscallSucceedsWithValue(buf.size())); - - EXPECT_THAT( - sendfile(sockets->first_fd(), in_fd.get(), nullptr, buf.size() + 1), - SyscallSucceedsWithValue(buf.size())); - std::vector<char> rbuf(buf.size() + 1); - ASSERT_THAT(read(sockets->second_fd(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(buf.size())); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} -#endif // __linux__ - -TEST_P(TCPSocketPairTest, SetTCPWindowClampBelowMinRcvBufConnectedSocket) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - // Discover minimum receive buf by setting a really low value - // for the receive buffer. - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &kZero, - sizeof(kZero)), - SyscallSucceeds()); - - // Now retrieve the minimum value for SO_RCVBUF as the set above should - // have caused SO_RCVBUF for the socket to be set to the minimum. - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - int min_so_rcvbuf = get; - - { - // Setting TCP_WINDOW_CLAMP to zero for a connected socket is not permitted. - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_WINDOW_CLAMP, - &kZero, sizeof(kZero)), - SyscallFailsWithErrno(EINVAL)); - - // Non-zero clamp values below MIN_SO_RCVBUF/2 should result in the clamp - // being set to MIN_SO_RCVBUF/2. - int below_half_min_so_rcvbuf = min_so_rcvbuf / 2 - 1; - EXPECT_THAT( - setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_WINDOW_CLAMP, - &below_half_min_so_rcvbuf, sizeof(below_half_min_so_rcvbuf)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_WINDOW_CLAMP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(min_so_rcvbuf / 2, get); - } -} - -TEST_P(TCPSocketPairTest, IpMulticastTtlDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_GT(get, 0); -} - -TEST_P(TCPSocketPairTest, IpMulticastLoopDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 1); -} - -TEST_P(TCPSocketPairTest, TCPResetDuringClose_NoRandomSave) { - DisableSave ds; // Too many syscalls. - constexpr int kThreadCount = 1000; - std::unique_ptr<ScopedThread> instances[kThreadCount]; - for (int i = 0; i < kThreadCount; i++) { - instances[i] = absl::make_unique<ScopedThread>([&]() { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ScopedThread t([&]() { - // Close one end to trigger sending of a FIN. - struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0}; - // Wait up to 20 seconds for the data. - constexpr int kPollTimeoutMs = 20000; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); - }); - - // Send some data then close. - constexpr char kStr[] = "abc"; - ASSERT_THAT(write(sockets->first_fd(), kStr, 3), - SyscallSucceedsWithValue(3)); - absl::SleepFor(absl::Milliseconds(10)); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - t.Join(); - }); - } - for (int i = 0; i < kThreadCount; i++) { - instances[i]->Join(); - } -} - -// Test setsockopt and getsockopt for a socket with SO_LINGER option. -TEST_P(TCPSocketPairTest, SetAndGetLingerOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Check getsockopt before SO_LINGER option is set. - struct linger got_linger = {-1, -1}; - socklen_t got_len = sizeof(got_linger); - - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_THAT(got_len, sizeof(got_linger)); - struct linger want_linger = {}; - EXPECT_EQ(0, memcmp(&want_linger, &got_linger, got_len)); - - // Set and get SO_LINGER with negative values. - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = -3; - ASSERT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_EQ(got_len, sizeof(got_linger)); - EXPECT_EQ(sl.l_onoff, got_linger.l_onoff); - // Linux returns a different value as it uses HZ to convert the seconds to - // jiffies which overflows for negative values. We want to be compatible with - // linux for getsockopt return value. - if (IsRunningOnGvisor()) { - EXPECT_EQ(sl.l_linger, got_linger.l_linger); - } - - // Set and get SO_LINGER option with positive values. - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_EQ(got_len, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len)); -} - -// Test socket to disable SO_LINGER option. -TEST_P(TCPSocketPairTest, SetOffLingerOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Set the SO_LINGER option. - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - - // Check getsockopt after SO_LINGER option is set. - struct linger got_linger = {-1, -1}; - socklen_t got_len = sizeof(got_linger); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_EQ(got_len, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len)); - - sl.l_onoff = 0; - sl.l_linger = 5; - ASSERT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - - // Check getsockopt after SO_LINGER option is set to zero. - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_EQ(got_len, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len)); -} - -// Test close on dup'd socket with SO_LINGER option set. -TEST_P(TCPSocketPairTest, CloseWithLingerOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Set the SO_LINGER option. - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - - // Check getsockopt after SO_LINGER option is set. - struct linger got_linger = {-1, -1}; - socklen_t got_len = sizeof(got_linger); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &got_len), - SyscallSucceeds()); - ASSERT_EQ(got_len, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len)); - - FileDescriptor dupFd = FileDescriptor(dup(sockets->first_fd())); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - char buf[10] = {}; - // Write on dupFd should succeed as socket will not be closed until - // all references are removed. - ASSERT_THAT(RetryEINTR(write)(dupFd.get(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(EBADF)); - - // Close the socket. - dupFd.reset(); - // Write on dupFd should fail as all references for socket are removed. - ASSERT_THAT(RetryEINTR(write)(dupFd.get(), buf, sizeof(buf)), - SyscallFailsWithErrno(EBADF)); -} -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_generic.h b/test/syscalls/linux/socket_ip_tcp_generic.h deleted file mode 100644 index a3eff3c73..000000000 --- a/test/syscalls/linux/socket_ip_tcp_generic.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected TCP sockets. -using TCPSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_TCP_GENERIC_H_ diff --git a/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc b/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc deleted file mode 100644 index 4e79d21f4..000000000 --- a/test/syscalls/linux/socket_ip_tcp_generic_loopback.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_ip_tcp_generic.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVecToVec<SocketPairKind>( - std::vector<Middleware>{ - NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)}, - std::vector<SocketPairKind>{ - IPv6TCPAcceptBindSocketPair(0), - IPv4TCPAcceptBindSocketPair(0), - DualStackTCPAcceptBindSocketPair(0), - }); -} - -INSTANTIATE_TEST_SUITE_P( - AllTCPSockets, TCPSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_loopback.cc b/test/syscalls/linux/socket_ip_tcp_loopback.cc deleted file mode 100644 index 9db3037bc..000000000 --- a/test/syscalls/linux/socket_ip_tcp_loopback.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_generic.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - IPv6TCPAcceptBindSocketPair(0), - IPv4TCPAcceptBindSocketPair(0), - DualStackTCPAcceptBindSocketPair(0), - }; -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, AllSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc b/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc deleted file mode 100644 index f996b93d2..000000000 --- a/test/syscalls/linux/socket_ip_tcp_loopback_blocking.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_stream_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVecToVec<SocketPairKind>( - std::vector<Middleware>{ - NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)}, - std::vector<SocketPairKind>{ - IPv6TCPAcceptBindSocketPair(0), - IPv4TCPAcceptBindSocketPair(0), - DualStackTCPAcceptBindSocketPair(0), - }); -} - -INSTANTIATE_TEST_SUITE_P( - BlockingTCPSockets, BlockingStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc b/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc deleted file mode 100644 index ffa377210..000000000 --- a/test/syscalls/linux/socket_ip_tcp_loopback_nonblock.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/tcp.h> - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_non_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVecToVec<SocketPairKind>( - std::vector<Middleware>{ - NoOp, SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &kSockOptOn)}, - std::vector<SocketPairKind>{ - IPv6TCPAcceptBindSocketPair(SOCK_NONBLOCK), - IPv4TCPAcceptBindSocketPair(SOCK_NONBLOCK), - }); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingTCPSockets, NonBlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_tcp_udp_generic.cc b/test/syscalls/linux/socket_ip_tcp_udp_generic.cc deleted file mode 100644 index f178f1af9..000000000 --- a/test/syscalls/linux/socket_ip_tcp_udp_generic.cc +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <poll.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of TCP and UDP sockets. -using TcpUdpSocketPairTest = SocketPairTest; - -TEST_P(TcpUdpSocketPairTest, ShutdownWrFollowedBySendIsError) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Now shutdown the write end of the first. - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_WR), SyscallSucceeds()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EPIPE)); -} - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - IPv6UDPBidirectionalBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - IPv4UDPBidirectionalBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - DualStackUDPBidirectionalBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - IPv6TCPAcceptBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - IPv4TCPAcceptBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - DualStackTCPAcceptBindSocketPair, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}))); -} - -INSTANTIATE_TEST_SUITE_P( - AllIPSockets, TcpUdpSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc deleted file mode 100644 index 1694e188a..000000000 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ /dev/null @@ -1,545 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "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> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(UDPSocketPairTest, MulticastTTLDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 1); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLMin) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kMin = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kMin, sizeof(kMin)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kMin); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kMax = 255; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kMax, sizeof(kMax)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kMax); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLNegativeOne) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kArbitrary = 6; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kArbitrary, sizeof(kArbitrary)), - SyscallSucceeds()); - - constexpr int kNegOne = -1; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kNegOne, sizeof(kNegOne)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 1); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLBelowMin) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kBelowMin = -2; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kBelowMin, sizeof(kBelowMin)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLAboveMax) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr int kAboveMax = 256; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kAboveMax, sizeof(kAboveMax)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UDPSocketPairTest, SetUDPMulticastTTLChar) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr char kArbitrary = 6; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &kArbitrary, sizeof(kArbitrary)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kArbitrary); -} - -TEST_P(UDPSocketPairTest, SetEmptyIPAddMembership) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct ip_mreqn req = {}; - EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &req, sizeof(req)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UDPSocketPairTest, MulticastLoopDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(UDPSocketPairTest, SetMulticastLoop) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(UDPSocketPairTest, SetMulticastLoopChar) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - constexpr char kSockOptOnChar = kSockOptOn; - constexpr char kSockOptOffChar = kSockOptOff; - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOffChar, sizeof(kSockOptOffChar)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); - - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOnChar, sizeof(kSockOptOnChar)), - SyscallSucceeds()); - - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP, - &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -TEST_P(UDPSocketPairTest, ReuseAddrDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(UDPSocketPairTest, SetReuseAddr) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(UDPSocketPairTest, ReusePortDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(UDPSocketPairTest, SetReusePort) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(UDPSocketPairTest, SetReuseAddrReusePort) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEPORT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -// Test getsockopt for a socket which is not set with IP_PKTINFO option. -TEST_P(UDPSocketPairTest, IPPKTINFODefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_IP, IP_PKTINFO, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -// Test setsockopt and getsockopt for a socket with IP_PKTINFO option. -TEST_P(UDPSocketPairTest, SetAndGetIPPKTINFO) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int level = SOL_IP; - int type = IP_PKTINFO; - - // 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)); -} - -// 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; - int option; -}; - -RecvTosOption GetRecvTosOption(int domain) { - TEST_CHECK(domain == AF_INET || domain == AF_INET6); - RecvTosOption opt; - switch (domain) { - case AF_INET: - opt.level = IPPROTO_IP; - opt.option = IP_RECVTOS; - break; - case AF_INET6: - opt.level = IPPROTO_IPV6; - opt.option = IPV6_RECVTCLASS; - break; - } - return opt; -} - -// Ensure that Receiving TOS or TCLASS is off by default. -TEST_P(UDPSocketPairTest, RecvTosDefault) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - RecvTosOption t = GetRecvTosOption(GetParam().domain); - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), t.level, t.option, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -// Test that setting and getting IP_RECVTOS or IPV6_RECVTCLASS works as -// expected. -TEST_P(UDPSocketPairTest, SetRecvTos) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - RecvTosOption t = GetRecvTosOption(GetParam().domain); - - ASSERT_THAT(setsockopt(sockets->first_fd(), t.level, t.option, &kSockOptOff, - sizeof(kSockOptOff)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(sockets->first_fd(), t.level, t.option, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); - - ASSERT_THAT(setsockopt(sockets->first_fd(), t.level, t.option, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), t.level, t.option, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); -} - -// Test that any socket (including IPv6 only) accepts the IPv4 TOS option: this -// mirrors behavior in linux. -TEST_P(UDPSocketPairTest, TOSRecvMismatch) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - RecvTosOption t = GetRecvTosOption(AF_INET); - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT( - getsockopt(sockets->first_fd(), t.level, t.option, &get, &get_len), - SyscallSucceedsWithValue(0)); -} - -// Test that an IPv4 socket does not support the IPv6 TClass option. -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)); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IPV6, IPV6_RECVTCLASS, - &get, &get_len), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -// Test the SO_LINGER option can be set/get on udp socket. -TEST_P(UDPSocketPairTest, SetAndGetSocketLinger) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int level = SOL_SOCKET; - int type = SO_LINGER; - - struct linger sl; - sl.l_onoff = 1; - sl.l_linger = 5; - ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &sl, sizeof(sl)), - SyscallSucceedsWithValue(0)); - - struct linger got_linger = {}; - socklen_t length = sizeof(sl); - ASSERT_THAT( - getsockopt(sockets->first_fd(), level, type, &got_linger, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&sl, &got_linger, length)); -} - -// Test getsockopt for SO_ACCEPTCONN on udp socket. -TEST_P(UDPSocketPairTest, GetSocketAcceptConn) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.h b/test/syscalls/linux/socket_ip_udp_generic.h deleted file mode 100644 index 106c54e9f..000000000 --- a/test/syscalls/linux/socket_ip_udp_generic.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected UDP sockets. -using UDPSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_GENERIC_H_ diff --git a/test/syscalls/linux/socket_ip_udp_loopback.cc b/test/syscalls/linux/socket_ip_udp_loopback.cc deleted file mode 100644 index c7fa44884..000000000 --- a/test/syscalls/linux/socket_ip_udp_loopback.cc +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_generic.h" -#include "test/syscalls/linux/socket_ip_udp_generic.h" -#include "test/syscalls/linux/socket_non_stream.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - IPv6UDPBidirectionalBindSocketPair(0), - IPv4UDPBidirectionalBindSocketPair(0), - DualStackUDPBidirectionalBindSocketPair(0), - }; -} - -INSTANTIATE_TEST_SUITE_P( - AllUDPSockets, AllSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - AllUDPSockets, NonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - AllUDPSockets, UDPSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc b/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc deleted file mode 100644 index d6925a8df..000000000 --- a/test/syscalls/linux/socket_ip_udp_loopback_blocking.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_non_stream_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - IPv6UDPBidirectionalBindSocketPair(0), - IPv4UDPBidirectionalBindSocketPair(0), - }; -} - -INSTANTIATE_TEST_SUITE_P( - BlockingUDPSockets, BlockingNonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc b/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc deleted file mode 100644 index d675eddc6..000000000 --- a/test/syscalls/linux/socket_ip_udp_loopback_nonblock.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_non_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - IPv6UDPBidirectionalBindSocketPair(SOCK_NONBLOCK), - IPv4UDPBidirectionalBindSocketPair(SOCK_NONBLOCK), - }; -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingUDPSockets, NonBlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc deleted file mode 100644 index fdbb2216b..000000000 --- a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc +++ /dev/null @@ -1,59 +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. - -#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" - -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -void IPUDPUnboundExternalNetworkingSocketTest::SetUp() { - // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its - // IPv4 address on eth0. - found_net_interfaces_ = false; - - // Get interface list. - ASSERT_NO_ERRNO(if_helper_.Load()); - std::vector<std::string> if_names = if_helper_.InterfaceList(AF_INET); - if (if_names.size() != 2) { - return; - } - - // Figure out which interface is where. - std::string lo = if_names[0]; - std::string eth = if_names[1]; - if (lo != "lo") std::swap(lo, eth); - if (lo != "lo") return; - - lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); - auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); - if (lo_if_addr == nullptr) { - return; - } - lo_if_addr_ = *reinterpret_cast<const sockaddr_in*>(lo_if_addr); - - eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); - auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); - if (eth_if_addr == nullptr) { - return; - } - eth_if_addr_ = *reinterpret_cast<const sockaddr_in*>(eth_if_addr); - - found_net_interfaces_ = true; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h deleted file mode 100644 index e5287addb..000000000 --- a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h +++ /dev/null @@ -1,46 +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. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to unbound IP UDP sockets in a sandbox -// with external networking support. -class IPUDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { - protected: - void SetUp() override; - - IfAddrHelper if_helper_; - - // found_net_interfaces_ is set to false if SetUp() could not obtain - // all interface infos that we need. - bool found_net_interfaces_; - - // Interface infos. - int lo_if_idx_; - int eth_if_idx_; - sockaddr_in lo_if_addr_; - sockaddr_in eth_if_addr_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ip_unbound.cc b/test/syscalls/linux/socket_ip_unbound.cc deleted file mode 100644 index 029f1e872..000000000 --- a/test/syscalls/linux/socket_ip_unbound.cc +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> -#include <cstring> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of IP sockets. -using IPUnboundSocketTest = SimpleSocketTest; - -TEST_P(IPUnboundSocketTest, TtlDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get = -1; - socklen_t get_sz = sizeof(get); - EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_TRUE(get == 64 || get == 127); - EXPECT_EQ(get_sz, sizeof(get)); -} - -TEST_P(IPUnboundSocketTest, SetTtl) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get1 = -1; - socklen_t get1_sz = sizeof(get1); - EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get1, &get1_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get1_sz, sizeof(get1)); - - int set = 100; - if (set == get1) { - set += 1; - } - socklen_t set_sz = sizeof(set); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set, set_sz), - SyscallSucceedsWithValue(0)); - - int get2 = -1; - socklen_t get2_sz = sizeof(get2); - EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get2, &get2_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get2_sz, sizeof(get2)); - EXPECT_EQ(get2, set); -} - -TEST_P(IPUnboundSocketTest, ResetTtlToDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get1 = -1; - socklen_t get1_sz = sizeof(get1); - EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get1, &get1_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get1_sz, sizeof(get1)); - - int set1 = 100; - if (set1 == get1) { - set1 += 1; - } - socklen_t set1_sz = sizeof(set1); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set1, set1_sz), - SyscallSucceedsWithValue(0)); - - int set2 = -1; - socklen_t set2_sz = sizeof(set2); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set2, set2_sz), - SyscallSucceedsWithValue(0)); - - int get2 = -1; - socklen_t get2_sz = sizeof(get2); - EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get2, &get2_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get2_sz, sizeof(get2)); - EXPECT_EQ(get2, get1); -} - -TEST_P(IPUnboundSocketTest, ZeroTtl) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int set = 0; - socklen_t set_sz = sizeof(set); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(IPUnboundSocketTest, InvalidLargeTtl) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int set = 256; - socklen_t set_sz = sizeof(set); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(IPUnboundSocketTest, InvalidNegativeTtl) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int set = -2; - socklen_t set_sz = sizeof(set); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_TTL, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); -} - -struct TOSOption { - int level; - int option; - int cmsg_level; -}; - -constexpr int INET_ECN_MASK = 3; - -static TOSOption GetTOSOption(int domain) { - TOSOption opt; - switch (domain) { - case AF_INET: - opt.level = IPPROTO_IP; - opt.option = IP_TOS; - opt.cmsg_level = SOL_IP; - break; - case AF_INET6: - opt.level = IPPROTO_IPV6; - opt.option = IPV6_TCLASS; - opt.cmsg_level = SOL_IPV6; - break; - } - return opt; -} - -TEST_P(IPUnboundSocketTest, TOSDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - TOSOption t = GetTOSOption(GetParam().domain); - int get = -1; - socklen_t get_sz = sizeof(get); - constexpr int kDefaultTOS = 0; - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, kDefaultTOS); -} - -TEST_P(IPUnboundSocketTest, SetTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0xC0; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, set); -} - -TEST_P(IPUnboundSocketTest, ZeroTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, set); -} - -TEST_P(IPUnboundSocketTest, InvalidLargeTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - // Test with exceeding the byte space. - int set = 256; - constexpr int kDefaultTOS = 0; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - if (GetParam().domain == AF_INET) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - } else { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); - } - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, kDefaultTOS); -} - -TEST_P(IPUnboundSocketTest, CheckSkipECN) { - // Test is inconsistant on different kernels. - SKIP_IF(!IsRunningOnGvisor()); - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0xFF; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - int expect = static_cast<uint8_t>(set); - if (GetParam().protocol == IPPROTO_TCP) { - expect &= ~INET_ECN_MASK; - } - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, expect); -} - -TEST_P(IPUnboundSocketTest, ZeroTOSOptionSize) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0xC0; - socklen_t set_sz = 0; - TOSOption t = GetTOSOption(GetParam().domain); - if (GetParam().domain == AF_INET) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - } else { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); - } - int get = -1; - socklen_t get_sz = 0; - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, 0); - EXPECT_EQ(get, -1); -} - -TEST_P(IPUnboundSocketTest, SmallTOSOptionSize) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0xC0; - constexpr int kDefaultTOS = 0; - TOSOption t = GetTOSOption(GetParam().domain); - for (socklen_t i = 1; i < sizeof(int); i++) { - int expect_tos; - socklen_t expect_sz; - if (GetParam().domain == AF_INET) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, i), - SyscallSucceedsWithValue(0)); - expect_tos = set; - expect_sz = sizeof(uint8_t); - } else { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, i), - SyscallFailsWithErrno(EINVAL)); - expect_tos = kDefaultTOS; - expect_sz = i; - } - uint get = -1; - socklen_t get_sz = i; - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, expect_sz); - // Account for partial copies by getsockopt, retrieve the lower - // bits specified by get_sz, while comparing against expect_tos. - EXPECT_EQ(get & ~(~0 << (get_sz * 8)), expect_tos); - } -} - -TEST_P(IPUnboundSocketTest, LargeTOSOptionSize) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = 0xC0; - TOSOption t = GetTOSOption(GetParam().domain); - for (socklen_t i = sizeof(int); i < 10; i++) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, i), - SyscallSucceedsWithValue(0)); - int get = -1; - socklen_t get_sz = i; - // We expect the system call handler to only copy atmost sizeof(int) bytes - // as asserted by the check below. Hence, we do not expect the copy to - // overflow in getsockopt. - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(int)); - EXPECT_EQ(get, set); - } -} - -TEST_P(IPUnboundSocketTest, NegativeTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int set = -1; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - int expect; - if (GetParam().domain == AF_INET) { - expect = static_cast<uint8_t>(set); - if (GetParam().protocol == IPPROTO_TCP) { - expect &= ~INET_ECN_MASK; - } - } else { - // On IPv6 TCLASS, setting -1 has the effect of resetting the - // TrafficClass. - expect = 0; - } - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, expect); -} - -TEST_P(IPUnboundSocketTest, InvalidNegativeTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - int set = -2; - socklen_t set_sz = sizeof(set); - TOSOption t = GetTOSOption(GetParam().domain); - int expect; - if (GetParam().domain == AF_INET) { - ASSERT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallSucceedsWithValue(0)); - expect = static_cast<uint8_t>(set); - if (GetParam().protocol == IPPROTO_TCP) { - expect &= ~INET_ECN_MASK; - } - } else { - ASSERT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz), - SyscallFailsWithErrno(EINVAL)); - expect = 0; - } - int get = 0; - socklen_t get_sz = sizeof(get); - ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_sz, sizeof(get)); - EXPECT_EQ(get, expect); -} - -TEST_P(IPUnboundSocketTest, NullTOS) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - TOSOption t = GetTOSOption(GetParam().domain); - int set_sz = sizeof(int); - if (GetParam().domain == AF_INET) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, nullptr, set_sz), - SyscallFailsWithErrno(EFAULT)); - } else { // AF_INET6 - // The AF_INET6 behavior is not yet compatible. gVisor will try to read - // optval from user memory at syscall handler, it needs substantial - // refactoring to implement this behavior just for IPv6. - if (IsRunningOnGvisor()) { - EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, nullptr, set_sz), - SyscallFailsWithErrno(EFAULT)); - } else { - // Linux's IPv6 stack treats nullptr optval as input of 0, so the call - // succeeds. (net/ipv6/ipv6_sockglue.c, do_ipv6_setsockopt()) - // - // Linux's implementation would need fixing as passing a nullptr as optval - // and non-zero optlen may not be valid. - // TODO(b/158666797): Combine the gVisor and linux cases for IPv6. - // Some kernel versions return EFAULT, so we handle both. - EXPECT_THAT( - setsockopt(socket->get(), t.level, t.option, nullptr, set_sz), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(0))); - } - } - socklen_t get_sz = sizeof(int); - EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, nullptr, &get_sz), - SyscallFailsWithErrno(EFAULT)); - int get = -1; - EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, nullptr), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_P(IPUnboundSocketTest, InsufficientBufferTOS) { - SKIP_IF(GetParam().protocol == IPPROTO_TCP); - - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - TOSOption t = GetTOSOption(GetParam().domain); - - in_addr addr4; - in6_addr addr6; - ASSERT_THAT(inet_pton(AF_INET, "127.0.0.1", &addr4), ::testing::Eq(1)); - ASSERT_THAT(inet_pton(AF_INET6, "fe80::", &addr6), ::testing::Eq(1)); - - cmsghdr cmsg = {}; - cmsg.cmsg_len = sizeof(cmsg); - cmsg.cmsg_level = t.cmsg_level; - cmsg.cmsg_type = t.option; - - msghdr msg = {}; - msg.msg_control = &cmsg; - msg.msg_controllen = sizeof(cmsg); - if (GetParam().domain == AF_INET) { - msg.msg_name = &addr4; - msg.msg_namelen = sizeof(addr4); - } else { - msg.msg_name = &addr6; - msg.msg_namelen = sizeof(addr6); - } - - EXPECT_THAT(sendmsg(socket->get(), &msg, 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(IPUnboundSocketTest, ReuseAddrDefault) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOff); - EXPECT_EQ(get_sz, sizeof(get)); -} - -TEST_P(IPUnboundSocketTest, SetReuseAddr) { - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ASSERT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - int get = -1; - socklen_t get_sz = sizeof(get); - ASSERT_THAT( - getsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &get, &get_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get, kSockOptOn); - EXPECT_EQ(get_sz, sizeof(get)); -} - -INSTANTIATE_TEST_SUITE_P( - IPUnboundSockets, IPUnboundSocketTest, - ::testing::ValuesIn(VecCat<SocketKind>( - ApplyVec<SocketKind>(IPv4UDPUnboundSocket, - std::vector<int>{0, SOCK_NONBLOCK}), - ApplyVec<SocketKind>(IPv6UDPUnboundSocket, - std::vector<int>{0, SOCK_NONBLOCK}), - ApplyVec<SocketKind>(IPv4TCPUnboundSocket, - std::vector{0, SOCK_NONBLOCK}), - ApplyVec<SocketKind>(IPv6TCPUnboundSocket, - std::vector{0, SOCK_NONBLOCK})))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_unbound_netlink.cc b/test/syscalls/linux/socket_ip_unbound_netlink.cc deleted file mode 100644 index 7fb1c0faf..000000000 --- a/test/syscalls/linux/socket_ip_unbound_netlink.cc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> -#include <cstring> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_netlink_route_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of IP sockets. -using IPv6UnboundSocketTest = SimpleSocketTest; - -TEST_P(IPv6UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved - // across save/restore. - DisableSave ds; - - // Delete the loopback address from the loopback interface. - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET6, - /*prefixlen=*/128, &in6addr_loopback, - sizeof(in6addr_loopback))); - Cleanup defer_addr_removal = - Cleanup([loopback_link = std::move(loopback_link)] { - EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6, - /*prefixlen=*/128, &in6addr_loopback, - sizeof(in6addr_loopback))); - }); - - TestAddress addr = V6Loopback(); - reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = 65535; - auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv6UnboundSocketTest, - ::testing::ValuesIn(std::vector<SocketKind>{ - IPv6UDPUnboundSocket(0), - IPv6TCPUnboundSocket(0)})); - -using IPv4UnboundSocketTest = SimpleSocketTest; - -TEST_P(IPv4UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved - // across save/restore. - DisableSave ds; - - // Delete the loopback address from the loopback interface. - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - struct in_addr laddr; - laddr.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/8, &laddr, sizeof(laddr))); - Cleanup defer_addr_removal = Cleanup( - [loopback_link = std::move(loopback_link), addr = std::move(laddr)] { - EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/8, &addr, sizeof(addr))); - }); - TestAddress addr = V4Loopback(); - reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = 65535; - auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(ENETUNREACH)); -} - -INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv4UnboundSocketTest, - ::testing::ValuesIn(std::vector<SocketKind>{ - IPv4UDPUnboundSocket(0), - IPv4TCPUnboundSocket(0)})); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc deleted file mode 100644 index 8eec31a46..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ /dev/null @@ -1,2620 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_ipv4_udp_unbound.h" - -#include <arpa/inet.h> -#include <net/if.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <cstdio> - -#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 { - -// Check that packets are not received without a group membership. Default send -// interface configured by bind. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNoGroup) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - EXPECT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address. If multicast worked like unicast, - // this would ensure that we get the packet. - auto receiver_addr = V4Any(); - EXPECT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Send the multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -// Check that not setting a default send interface prevents multicast packets -// from being sent. Group membership interface configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddrNoDefaultSendIf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive any - // unicast packet. - auto receiver_addr = V4Any(); - EXPECT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallFailsWithErrno(ENETUNREACH)); -} - -// Check that not setting a default send interface prevents multicast packets -// from being sent. Group membership interface configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNicNoDefaultSendIf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive any - // unicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallFailsWithErrno(ENETUNREACH)); -} - -// Check that multicast works when the default send interface is configured by -// bind and the group membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// bind and the group membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in connect, and the group -// membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrConnect) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto connect_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - ASSERT_THAT( - RetryEINTR(connect)(socket1->get(), - reinterpret_cast<sockaddr*>(&connect_addr.addr), - connect_addr.addr_len), - SyscallSucceeds()); - - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(send)(socket1->get(), send_buf, sizeof(send_buf), 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in connect, and the group -// membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto connect_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - ASSERT_THAT( - RetryEINTR(connect)(socket1->get(), - reinterpret_cast<sockaddr*>(&connect_addr.addr), - connect_addr.addr_len), - SyscallSucceeds()); - - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(send)(socket1->get(), send_buf, sizeof(send_buf), 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the first FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the first FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in connect, and the group -// membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfConnect) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the first FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto connect_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - EXPECT_THAT( - RetryEINTR(connect)(socket1->get(), - reinterpret_cast<sockaddr*>(&connect_addr.addr), - connect_addr.addr_len), - SyscallSucceeds()); - - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(send)(socket1->get(), send_buf, sizeof(send_buf), 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in connect, and the group -// membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Bind the first FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto connect_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - ASSERT_THAT( - RetryEINTR(connect)(socket1->get(), - reinterpret_cast<sockaddr*>(&connect_addr.addr), - connect_addr.addr_len), - SyscallSucceeds()); - - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(send)(socket1->get(), send_buf, sizeof(send_buf), 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfNoLoop) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Bind the first FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast works when the default send interface is configured by -// IP_MULTICAST_IF, the send address is specified in sendto, and the group -// membership is configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Set the default send interface. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that dropping a group membership that does not exist fails. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastInvalidDrop) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Unregister from a membership that we didn't have. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -// Check that dropping a group membership prevents multicast packets from being -// delivered. Default send address configured by bind and group membership -// interface configured by address. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - EXPECT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - EXPECT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register and unregister to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -// Check that dropping a group membership prevents multicast packets from being -// delivered. Default send address configured by bind and group membership -// interface configured by NIC ID. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - EXPECT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - EXPECT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register and unregister to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfZero) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn iface = {}; - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfInvalidNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn iface = {}; - iface.imr_ifindex = -1; - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfInvalidAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq iface = {}; - iface.imr_interface.s_addr = inet_addr("255.255.255"); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetShort) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Create a valid full-sized request. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - - // Send an optlen of 1 to check that optlen is enforced. - EXPECT_THAT( - setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, 1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfDefault) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfDefaultReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - - // getsockopt(IP_MULTICAST_IF) will only return the interface address which - // hasn't been set. - EXPECT_EQ(get.imr_multiaddr.s_addr, 0); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetAddrGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr set = {}; - set.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, set.s_addr); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetReqAddrGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq set = {}; - set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, set.imr_interface.s_addr); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetNicGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn set = {}; - set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, 0); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr set = {}; - set.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, set.s_addr); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetReqAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq set = {}; - set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, set.imr_interface.s_addr); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn set = {}; - set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, TestJoinGroupNoIf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(ENODEV)); -} - -TEST_P(IPv4UDPUnboundSocketTest, TestJoinGroupInvalidIf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn group = {}; - group.imr_address.s_addr = inet_addr("255.255.255"); - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(ENODEV)); -} - -// Check that multiple memberships are not allowed on the same socket. -TEST_P(IPv4UDPUnboundSocketTest, TestMultipleJoinsOnSingleSocket) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto fd = socket1->get(); - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - - EXPECT_THAT( - setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), - SyscallSucceeds()); - - EXPECT_THAT( - setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), - SyscallFailsWithErrno(EADDRINUSE)); -} - -// Check that two sockets can join the same multicast group at the same time. -TEST_P(IPv4UDPUnboundSocketTest, TestTwoSocketsJoinSameMulticastGroup) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Drop the membership twice on each socket, the second call for each socket - // should fail. - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -// Check that two sockets can join the same multicast group at the same time, -// and both will receive data on it. -TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionOnTwoSockets) { - std::unique_ptr<SocketPair> socket_pairs[2] = { - absl::make_unique<FDSocketPair>(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket())), - absl::make_unique<FDSocketPair>(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket()))}; - - ip_mreq iface = {}, group = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - auto receiver_addr = V4Any(); - int bound_port = 0; - - // Create two socketpairs with the exact same configuration. - for (auto& sockets : socket_pairs) { - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, - &iface, sizeof(iface)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - ASSERT_THAT(bind(sockets->second_fd(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - // Get the port assigned. - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(sockets->second_fd(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - // On the first iteration, save the port we are bound to. On the second - // iteration, verify the port is the same as the one from the first - // iteration. In other words, both sockets listen on the same port. - if (bound_port == 0) { - bound_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - } else { - EXPECT_EQ(bound_port, - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port); - } - } - - // Send a multicast packet to the group from two different sockets and verify - // it is received by both sockets that joined that group. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port; - for (auto& sockets : socket_pairs) { - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT( - RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet on both sockets. - for (auto& sockets : socket_pairs) { - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } - } -} - -// Check that on two sockets that joined a group and listen on ANY, dropping -// memberships one by one will continue to deliver packets to both sockets until -// both memberships have been dropped. -TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) { - std::unique_ptr<SocketPair> socket_pairs[2] = { - absl::make_unique<FDSocketPair>(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket())), - absl::make_unique<FDSocketPair>(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket()))}; - - ip_mreq iface = {}, group = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - auto receiver_addr = V4Any(); - int bound_port = 0; - - // Create two socketpairs with the exact same configuration. - for (auto& sockets : socket_pairs) { - ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, - &iface, sizeof(iface)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - ASSERT_THAT(bind(sockets->second_fd(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - // Get the port assigned. - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(sockets->second_fd(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - // On the first iteration, save the port we are bound to. On the second - // iteration, verify the port is the same as the one from the first - // iteration. In other words, both sockets listen on the same port. - if (bound_port == 0) { - bound_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - } else { - EXPECT_EQ(bound_port, - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port); - } - } - - // Drop the membership of the first socket pair and verify data is still - // received. - ASSERT_THAT(setsockopt(socket_pairs[0]->second_fd(), IPPROTO_IP, - IP_DROP_MEMBERSHIP, &group, sizeof(group)), - SyscallSucceeds()); - // Send a packet from each socket_pair. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port; - for (auto& sockets : socket_pairs) { - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT( - RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet on both sockets. - for (auto& sockets : socket_pairs) { - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } - } - - // Drop the membership of the second socket pair and verify data stops being - // received. - ASSERT_THAT(setsockopt(socket_pairs[1]->second_fd(), IPPROTO_IP, - IP_DROP_MEMBERSHIP, &group, sizeof(group)), - SyscallSucceeds()); - // Send a packet from each socket_pair. - for (auto& sockets : socket_pairs) { - char send_buf[200]; - ASSERT_THAT( - RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - char recv_buf[sizeof(send_buf)] = {}; - for (auto& sockets : socket_pairs) { - ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf), - 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); - } - } -} - -// Check that a receiving socket can bind to the multicast address before -// joining the group and receive data once the group has been joined. -TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind second socket (receiver) to the multicast address. - auto receiver_addr = V4Multicast(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - // Update receiver_addr with the correct port number. - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet on the first socket out the loopback interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - auto sendto_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that a receiving socket can bind to the multicast address and won't -// receive multicast data if it hasn't joined the group. -TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenNoJoinThenNoReceive) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind second socket (receiver) to the multicast address. - auto receiver_addr = V4Multicast(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - // Update receiver_addr with the correct port number. - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Send a multicast packet on the first socket out the loopback interface. - ip_mreq iface = {}; - iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - auto sendto_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we don't receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); -} - -// Check that a socket can bind to a multicast address and still send out -// packets. -TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenSend) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind second socket (receiver) to the ANY address. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Bind the first socket (sender) to the multicast address. - auto sender_addr = V4Multicast(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - socklen_t sender_addr_len = sender_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&sender_addr.addr), - &sender_addr_len), - SyscallSucceeds()); - EXPECT_EQ(sender_addr_len, sender_addr.addr_len); - - // Send a packet on the first socket to the loopback address. - auto sendto_addr = V4Loopback(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that a receiving socket can bind to the broadcast address and receive -// broadcast packets. -TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenReceive) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind second socket (receiver) to the broadcast address. - auto receiver_addr = V4Broadcast(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Send a broadcast packet on the first socket out the loopback interface. - EXPECT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - // Note: Binding to the loopback interface makes the broadcast go out of it. - auto sender_bind_addr = V4Loopback(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_bind_addr.addr), - sender_bind_addr.addr_len), - SyscallSucceeds()); - auto sendto_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that a socket can bind to the broadcast address and still send out -// packets. -TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind second socket (receiver) to the ANY address. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(socket2->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(socket2->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Bind the first socket (sender) to the broadcast address. - auto sender_addr = V4Broadcast(); - ASSERT_THAT( - bind(socket1->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - socklen_t sender_addr_len = sender_addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&sender_addr.addr), - &sender_addr_len), - SyscallSucceeds()); - EXPECT_EQ(sender_addr_len, sender_addr.addr_len); - - // Send a packet on the first socket to the loopback address. - auto sendto_addr = V4Loopback(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket1->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that SO_REUSEADDR always delivers to the most recently bound socket. -// -// FIXME(gvisor.dev/issue/873): Endpoint order is not restored correctly. Enable -// random and co-op save (below) once that is fixed. -TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) { - std::vector<std::unique_ptr<FileDescriptor>> sockets; - sockets.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(NewSocket())); - - ASSERT_THAT(setsockopt(sockets[0]->get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(sockets[0]->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(sockets[0]->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - constexpr int kMessageSize = 200; - - // FIXME(gvisor.dev/issue/873): Endpoint order is not restored correctly. - const DisableSave ds; - - for (int i = 0; i < 10; i++) { - // Add a new receiver. - sockets.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(NewSocket())); - auto& last = sockets.back(); - ASSERT_THAT(setsockopt(last->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(last->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Send a new message to the SO_REUSEADDR group. We use a new socket each - // time so that a new ephemeral port will be used each time. This ensures - // that we aren't doing REUSEPORT-like hash load blancing. - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - char send_buf[kMessageSize]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - EXPECT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Verify that the most recent socket got the message. We don't expect any - // of the other sockets to have received it, but we will check that later. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RecvTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(send_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } - - // Verify that no other messages were received. - for (auto& socket : sockets) { - char recv_buf[kMessageSize] = {}; - EXPECT_THAT( - RecvTimeout(socket->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - PosixErrorIs(EAGAIN, ::testing::_)); - } -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrThenReusePort) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReusePortThenReuseAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConvertibleToReusePort) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Bind socket3 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConvertibleToReuseAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Bind socket3 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConversionReversable1) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Close socket2 to revert to just socket1 with REUSEADDR and REUSEPORT. - socket2->reset(); - - // Bind socket3 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConversionReversable2) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Close socket2 to revert to just socket1 with REUSEADDR and REUSEPORT. - socket2->reset(); - - // Bind socket3 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindDoubleReuseAddrReusePortThenReusePort) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, also with REUSEADDR and - // REUSEPORT. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Bind socket3 to the same address as socket1, only with REUSEPORT. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); -} - -TEST_P(IPv4UDPUnboundSocketTest, BindDoubleReuseAddrReusePortThenReuseAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind socket1 with REUSEADDR and REUSEPORT. - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(socket1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(socket1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind socket2 to the same address as socket1, also with REUSEADDR and - // REUSEPORT. - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(socket2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - ASSERT_THAT(bind(socket2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - // Bind socket3 to the same address as socket1, only with REUSEADDR. - ASSERT_THAT(setsockopt(socket3->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(socket3->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); -} - -// Check that REUSEPORT takes precedence over REUSEADDR. -TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) { - auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ASSERT_THAT(setsockopt(receiver1->get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(receiver1->get(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(receiver1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(receiver1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Bind receiver2 to the same address as socket1, also with REUSEADDR and - // REUSEPORT. - ASSERT_THAT(setsockopt(receiver2->get(), SOL_SOCKET, SO_REUSEADDR, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(setsockopt(receiver2->get(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT(bind(receiver2->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - - constexpr int kMessageSize = 10; - - // Saving during each iteration of the following loop is too expensive. - DisableSave ds; - - for (int i = 0; i < 100; ++i) { - // Send a new message to the REUSEADDR/REUSEPORT group. We use a new socket - // each time so that a new ephemerial port will be used each time. This - // ensures that we cycle through hashes. - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - char send_buf[kMessageSize] = {}; - EXPECT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - } - - ds.reset(); - - // Check that both receivers got messages. This checks that we are using load - // balancing (REUSEPORT) instead of the most recently bound socket - // (REUSEADDR). - char recv_buf[kMessageSize] = {}; - EXPECT_THAT( - RecvTimeout(receiver1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(kMessageSize)); - EXPECT_THAT( - RecvTimeout(receiver2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/), - IsPosixErrorOkAndHolds(kMessageSize)); -} - -// Test that socket will receive packet info control message. -TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { - // TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet. - SKIP_IF((IsRunningWithHostinet())); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto sender_addr = V4Loopback(); - int level = SOL_IP; - int type = IP_PKTINFO; - - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - socklen_t sender_addr_len = sender_addr.addr_len; - ASSERT_THAT(getsockname(receiver->get(), - reinterpret_cast<sockaddr*>(&sender_addr.addr), - &sender_addr_len), - SyscallSucceeds()); - EXPECT_EQ(sender_addr_len, sender_addr.addr_len); - - auto receiver_addr = V4Loopback(); - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&sender_addr.addr)->sin_port; - ASSERT_THAT( - connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - - // Allow socket to receive 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(in_pktinfo))] = {}; - size_t cmsg_data_len = sizeof(in_pktinfo); - 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); - - // Get loopback index. - ifreq ifr = {}; - absl::SNPrintF(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(sender->get(), SIOCGIFINDEX, &ifr), SyscallSucceeds()); - ASSERT_NE(ifr.ifr_ifindex, 0); - - // Check the data - in_pktinfo received_pktinfo = {}; - memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(in_pktinfo)); - EXPECT_EQ(received_pktinfo.ipi_ifindex, ifr.ifr_ifindex); - EXPECT_EQ(received_pktinfo.ipi_spec_dst.s_addr, htonl(INADDR_LOOPBACK)); - 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) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Discover minimum buffer size by setting it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &below_min, - sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_RCVBUF above max is clamped to the maximum -// receive buffer size. -TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufAboveMax) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Discover maxmimum buffer size by setting to a really large value. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &above_max, - sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_RCVBUF min <= rcvBufSz <= max is honored. -TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBuf) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int max = 0; - int min = 0; - { - // Discover maxmimum buffer size by setting to a really large value. - constexpr int kRcvBufSz = 0xffffffff; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by setting it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &quarter_sz, - sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len), - SyscallSucceeds()); - - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - ASSERT_EQ(quarter_sz, val); -} - -// Check that setting SO_SNDBUF below min is clamped to the minimum -// send buffer size. -TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBufBelowMin) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Discover minimum buffer size by setting it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz, - sizeof(kSndBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - - // Linux doubles the value so let's use a value that when doubled will still - // be smaller than min. - int below_min = min / 2 - 1; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &below_min, - sizeof(below_min)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - ASSERT_EQ(min, val); -} - -// Check that setting SO_SNDBUF above max is clamped to the maximum -// send buffer size. -TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBufAboveMax) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Discover maxmimum buffer size by setting to a really large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz, - sizeof(kSndBufSz)), - SyscallSucceeds()); - - int max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - - int above_max = max + 1; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &above_max, - sizeof(above_max)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - ASSERT_EQ(max, val); -} - -// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. -TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBuf) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int max = 0; - int min = 0; - { - // Discover maxmimum buffer size by setting to a really large value. - constexpr int kSndBufSz = 0xffffffff; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz, - sizeof(kSndBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by setting it to zero. - constexpr int kSndBufSz = 0; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz, - sizeof(kSndBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &quarter_sz, - sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - quarter_sz *= 2; - ASSERT_EQ(quarter_sz, val); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIPPacketInfo) { - auto sender_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the first FD to the loopback. This is an alternative to - // IP_MULTICAST_IF for setting the default send interface. - auto sender_addr = V4Loopback(); - ASSERT_THAT( - bind(sender_socket->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT(bind(receiver_socket->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(receiver_socket->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(receiver_socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - - // Register to receive IP packet info. - const int one = 1; - ASSERT_THAT(setsockopt(receiver_socket->get(), IPPROTO_IP, IP_PKTINFO, &one, - sizeof(one)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT( - RetryEINTR(sendto)(sender_socket->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - msghdr recv_msg = {}; - iovec recv_iov = {}; - char recv_buf[sizeof(send_buf)]; - char recv_cmsg_buf[CMSG_SPACE(sizeof(in_pktinfo))] = {}; - size_t cmsg_data_len = sizeof(in_pktinfo); - recv_iov.iov_base = recv_buf; - recv_iov.iov_len = sizeof(recv_buf); - recv_msg.msg_iov = &recv_iov; - recv_msg.msg_iovlen = 1; - recv_msg.msg_controllen = CMSG_LEN(cmsg_data_len); - recv_msg.msg_control = recv_cmsg_buf; - ASSERT_THAT(RetryEINTR(recvmsg)(receiver_socket->get(), &recv_msg, 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - - // Check the IP_PKTINFO control message. - cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); - EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IP); - EXPECT_EQ(cmsg->cmsg_type, IP_PKTINFO); - - // Get loopback index. - ifreq ifr = {}; - absl::SNPrintF(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(receiver_socket->get(), SIOCGIFINDEX, &ifr), - SyscallSucceeds()); - ASSERT_NE(ifr.ifr_ifindex, 0); - - in_pktinfo received_pktinfo = {}; - memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(in_pktinfo)); - EXPECT_EQ(received_pktinfo.ipi_ifindex, ifr.ifr_ifindex); - if (IsRunningOnGvisor()) { - // This should actually be a unicast address assigned to the interface. - // - // TODO(gvisor.dev/issue/3556): This check is validating incorrect - // behaviour. We still include the test so that once the bug is - // resolved, this test will start to fail and the individual tasked - // with fixing this bug knows to also fix this test :). - EXPECT_EQ(received_pktinfo.ipi_spec_dst.s_addr, group.imr_multiaddr.s_addr); - } else { - EXPECT_EQ(received_pktinfo.ipi_spec_dst.s_addr, htonl(INADDR_LOOPBACK)); - } - EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, group.imr_multiaddr.s_addr); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.h b/test/syscalls/linux/socket_ipv4_udp_unbound.h deleted file mode 100644 index f64c57645..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to IPv4 UDP sockets. -using IPv4UDPUnboundSocketTest = SimpleSocketTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc deleted file mode 100644 index 940289d15..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc +++ /dev/null @@ -1,1030 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" - -namespace gvisor { -namespace testing { - -TestAddress V4EmptyAddress() { - TestAddress t("V4Empty"); - t.addr.ss_family = AF_INET; - t.addr_len = sizeof(sockaddr_in); - return t; -} - -// Verifies that a broadcast UDP packet will arrive at all UDP sockets with -// the destination port number. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - UDPBroadcastReceivedOnExpectedPort) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto norcv = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Enable SO_BROADCAST on the sending socket. - ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Enable SO_REUSEPORT on the receiving sockets so that they may both be bound - // to the broadcast messages destination port. - ASSERT_THAT(setsockopt(rcvr1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(setsockopt(rcvr2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Bind the first socket to the ANY address and let the system assign a port. - auto rcv1_addr = V4Any(); - ASSERT_THAT(bind(rcvr1->get(), reinterpret_cast<sockaddr*>(&rcv1_addr.addr), - rcv1_addr.addr_len), - SyscallSucceedsWithValue(0)); - // Retrieve port number from first socket so that it can be bound to the - // second socket. - socklen_t rcv_addr_sz = rcv1_addr.addr_len; - ASSERT_THAT( - getsockname(rcvr1->get(), reinterpret_cast<sockaddr*>(&rcv1_addr.addr), - &rcv_addr_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(rcv_addr_sz, rcv1_addr.addr_len); - auto port = reinterpret_cast<sockaddr_in*>(&rcv1_addr.addr)->sin_port; - - // Bind the second socket to the same address:port as the first. - ASSERT_THAT(bind(rcvr2->get(), reinterpret_cast<sockaddr*>(&rcv1_addr.addr), - rcv_addr_sz), - SyscallSucceedsWithValue(0)); - - // Bind the non-receiving socket to an ephemeral port. - auto norecv_addr = V4Any(); - ASSERT_THAT(bind(norcv->get(), reinterpret_cast<sockaddr*>(&norecv_addr.addr), - norecv_addr.addr_len), - SyscallSucceedsWithValue(0)); - - // Broadcast a test message. - auto dst_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&dst_addr.addr)->sin_port = port; - constexpr char kTestMsg[] = "hello, world"; - EXPECT_THAT( - sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<sockaddr*>(&dst_addr.addr), dst_addr.addr_len), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - - // Verify that the receiving sockets received the test message. - char buf[sizeof(kTestMsg)] = {}; - EXPECT_THAT(recv(rcvr1->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); - memset(buf, 0, sizeof(buf)); - EXPECT_THAT(recv(rcvr2->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); - - // Verify that the non-receiving socket did not receive the test message. - memset(buf, 0, sizeof(buf)); - EXPECT_THAT(RetryEINTR(recv)(norcv->get(), buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Verifies that a broadcast UDP packet will arrive at all UDP sockets bound to -// the destination port number and either INADDR_ANY or INADDR_BROADCAST, but -// not a unicast address. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - UDPBroadcastReceivedOnExpectedAddresses) { - SKIP_IF(!found_net_interfaces_); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto norcv = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Enable SO_BROADCAST on the sending socket. - ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Enable SO_REUSEPORT on all sockets so that they may all be bound to the - // broadcast messages destination port. - ASSERT_THAT(setsockopt(rcvr1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(setsockopt(rcvr2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - ASSERT_THAT(setsockopt(norcv->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Bind the first socket the ANY address and let the system assign a port. - auto rcv1_addr = V4Any(); - ASSERT_THAT(bind(rcvr1->get(), reinterpret_cast<sockaddr*>(&rcv1_addr.addr), - rcv1_addr.addr_len), - SyscallSucceedsWithValue(0)); - // Retrieve port number from first socket so that it can be bound to the - // second socket. - socklen_t rcv_addr_sz = rcv1_addr.addr_len; - ASSERT_THAT( - getsockname(rcvr1->get(), reinterpret_cast<sockaddr*>(&rcv1_addr.addr), - &rcv_addr_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(rcv_addr_sz, rcv1_addr.addr_len); - auto port = reinterpret_cast<sockaddr_in*>(&rcv1_addr.addr)->sin_port; - - // Bind the second socket to the broadcast address. - auto rcv2_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&rcv2_addr.addr)->sin_port = port; - ASSERT_THAT(bind(rcvr2->get(), reinterpret_cast<sockaddr*>(&rcv2_addr.addr), - rcv2_addr.addr_len), - SyscallSucceedsWithValue(0)); - - // Bind the non-receiving socket to the unicast ethernet address. - auto norecv_addr = rcv1_addr; - reinterpret_cast<sockaddr_in*>(&norecv_addr.addr)->sin_addr = - eth_if_addr_.sin_addr; - ASSERT_THAT(bind(norcv->get(), reinterpret_cast<sockaddr*>(&norecv_addr.addr), - norecv_addr.addr_len), - SyscallSucceedsWithValue(0)); - - // Broadcast a test message. - auto dst_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&dst_addr.addr)->sin_port = port; - constexpr char kTestMsg[] = "hello, world"; - EXPECT_THAT( - sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<sockaddr*>(&dst_addr.addr), dst_addr.addr_len), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - - // Verify that the receiving sockets received the test message. - char buf[sizeof(kTestMsg)] = {}; - EXPECT_THAT(recv(rcvr1->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); - memset(buf, 0, sizeof(buf)); - EXPECT_THAT(recv(rcvr2->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); - - // Verify that the non-receiving socket did not receive the test message. - memset(buf, 0, sizeof(buf)); - EXPECT_THAT(RetryEINTR(recv)(norcv->get(), buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Verifies that a UDP broadcast can be sent and then received back on the same -// socket that is bound to the broadcast address (255.255.255.255). -// FIXME(b/141938460): This can be combined with the next test -// (UDPBroadcastSendRecvOnSocketBoundToAny). -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - UDPBroadcastSendRecvOnSocketBoundToBroadcast) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Enable SO_BROADCAST. - ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Bind the sender to the broadcast address. - auto src_addr = V4Broadcast(); - ASSERT_THAT(bind(sender->get(), reinterpret_cast<sockaddr*>(&src_addr.addr), - src_addr.addr_len), - SyscallSucceedsWithValue(0)); - socklen_t src_sz = src_addr.addr_len; - ASSERT_THAT(getsockname(sender->get(), - reinterpret_cast<sockaddr*>(&src_addr.addr), &src_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(src_sz, src_addr.addr_len); - - // Send the message. - auto dst_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&dst_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&src_addr.addr)->sin_port; - constexpr char kTestMsg[] = "hello, world"; - EXPECT_THAT( - sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<sockaddr*>(&dst_addr.addr), dst_addr.addr_len), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - - // Verify that the message was received. - char buf[sizeof(kTestMsg)] = {}; - EXPECT_THAT(RetryEINTR(recv)(sender->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); -} - -// Verifies that a UDP broadcast can be sent and then received back on the same -// socket that is bound to the ANY address (0.0.0.0). -// FIXME(b/141938460): This can be combined with the previous test -// (UDPBroadcastSendRecvOnSocketBoundToBroadcast). -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - UDPBroadcastSendRecvOnSocketBoundToAny) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Enable SO_BROADCAST. - ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); - - // Bind the sender to the ANY address. - auto src_addr = V4Any(); - ASSERT_THAT(bind(sender->get(), reinterpret_cast<sockaddr*>(&src_addr.addr), - src_addr.addr_len), - SyscallSucceedsWithValue(0)); - socklen_t src_sz = src_addr.addr_len; - ASSERT_THAT(getsockname(sender->get(), - reinterpret_cast<sockaddr*>(&src_addr.addr), &src_sz), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(src_sz, src_addr.addr_len); - - // Send the message. - auto dst_addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&dst_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&src_addr.addr)->sin_port; - constexpr char kTestMsg[] = "hello, world"; - EXPECT_THAT( - sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<sockaddr*>(&dst_addr.addr), dst_addr.addr_len), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - - // Verify that the message was received. - char buf[sizeof(kTestMsg)] = {}; - EXPECT_THAT(RetryEINTR(recv)(sender->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg))); -} - -// Verifies that a UDP broadcast fails to send on a socket with SO_BROADCAST -// disabled. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendBroadcast) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Broadcast a test message without having enabled SO_BROADCAST on the sending - // socket. - auto addr = V4Broadcast(); - reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = htons(12345); - constexpr char kTestMsg[] = "hello, world"; - - EXPECT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<sockaddr*>(&addr.addr), addr.addr_len), - SyscallFailsWithErrno(EACCES)); -} - -// Verifies that a UDP unicast on an unbound socket reaches its destination. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendUnicastOnUnbound) { - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcvr = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the receiver and retrieve its address and port number. - sockaddr_in addr = {}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = htons(0); - ASSERT_THAT(bind(rcvr->get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceedsWithValue(0)); - memset(&addr, 0, sizeof(addr)); - socklen_t addr_sz = sizeof(addr); - ASSERT_THAT(getsockname(rcvr->get(), - reinterpret_cast<struct sockaddr*>(&addr), &addr_sz), - SyscallSucceedsWithValue(0)); - - // Send a test message to the receiver. - constexpr char kTestMsg[] = "hello, world"; - ASSERT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0, - reinterpret_cast<struct sockaddr*>(&addr), addr_sz), - SyscallSucceedsWithValue(sizeof(kTestMsg))); - char buf[sizeof(kTestMsg)] = {}; - ASSERT_THAT(recv(rcvr->get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(kTestMsg))); -} - -// Check that multicast packets won't be delivered to the sending socket with no -// set interface or group membership. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastSelfNoGroup) { - // FIXME(b/125485338): A group membership is not required for external - // multicast on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - SKIP_IF(!found_net_interfaces_); - - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - auto bind_addr = V4Any(); - ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - bind_addr.addr_len), - SyscallSucceeds()); - socklen_t bind_addr_len = bind_addr.addr_len; - ASSERT_THAT( - getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - &bind_addr_len), - SyscallSucceeds()); - EXPECT_EQ(bind_addr_len, bind_addr.addr_len); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Check that multicast packets will be delivered to the sending socket without -// setting an interface. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelf) { - SKIP_IF(!found_net_interfaces_); - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - auto bind_addr = V4Any(); - ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - bind_addr.addr_len), - SyscallSucceeds()); - socklen_t bind_addr_len = bind_addr.addr_len; - ASSERT_THAT( - getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - &bind_addr_len), - SyscallSucceeds()); - EXPECT_EQ(bind_addr_len, bind_addr.addr_len); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - ASSERT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast packets won't be delivered to the sending socket with no -// set interface and IP_MULTICAST_LOOP disabled. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastSelfLoopOff) { - SKIP_IF(!found_net_interfaces_); - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - auto bind_addr = V4Any(); - ASSERT_THAT(bind(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - bind_addr.addr_len), - SyscallSucceeds()); - socklen_t bind_addr_len = bind_addr.addr_len; - ASSERT_THAT( - getsockname(socket->get(), reinterpret_cast<sockaddr*>(&bind_addr.addr), - &bind_addr_len), - SyscallSucceeds()); - EXPECT_EQ(bind_addr_len, bind_addr.addr_len); - - // Disable multicast looping. - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Register to receive multicast packets. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&bind_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(socket->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - EXPECT_THAT( - RetryEINTR(recv)(socket->get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Check that multicast packets won't be delivered to another socket with no -// set interface or group membership. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastNoGroup) { - // FIXME(b/125485338): A group membership is not required for external - // multicast on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - SKIP_IF(!found_net_interfaces_); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Check that multicast packets will be delivered to another socket without -// setting an interface. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticast) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that multicast packets won't be delivered to another socket with no -// set interface and IP_MULTICAST_LOOP disabled on the sending socket. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastSenderNoLoop) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - - // Disable multicast looping on the sender. - EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we did not receive the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Check that multicast packets will be delivered to the sending socket without -// setting an interface and IP_MULTICAST_LOOP disabled on the receiving socket. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastReceiverNoLoop) { - SKIP_IF(!found_net_interfaces_); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Bind the second FD to the v4 any address to ensure that we can receive the - // multicast packet. - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - - // Disable multicast looping on the receiver. - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_MULTICAST_LOOP, - &kSockOptOff, sizeof(kSockOptOff)), - SyscallSucceeds()); - - // Register to receive multicast packets. - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); -} - -// Check that two sockets can join the same multicast group at the same time, -// and both will receive data on it when bound to the ANY address. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastToTwoBoundToAny) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - std::unique_ptr<FileDescriptor> receivers[2] = { - ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket())}; - - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - auto receiver_addr = V4Any(); - int bound_port = 0; - for (auto& receiver : receivers) { - ASSERT_THAT(setsockopt(receiver->get(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - // Bind to ANY to receive multicast packets. - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - EXPECT_EQ( - htonl(INADDR_ANY), - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_addr.s_addr); - // On the first iteration, save the port we are bound to. On the second - // iteration, verify the port is the same as the one from the first - // iteration. In other words, both sockets listen on the same port. - if (bound_port == 0) { - bound_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - } else { - EXPECT_EQ(bound_port, - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port); - } - - // Register to receive multicast packets. - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - } - - // Send a multicast packet to the group and verify both receivers get it. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - for (auto& receiver : receivers) { - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } -} - -// Check that two sockets can join the same multicast group at the same time, -// and both will receive data on it when bound to the multicast address. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastToTwoBoundToMulticastAddress) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - std::unique_ptr<FileDescriptor> receivers[2] = { - ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket())}; - - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - auto receiver_addr = V4Multicast(); - int bound_port = 0; - for (auto& receiver : receivers) { - ASSERT_THAT(setsockopt(receiver->get(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - EXPECT_EQ( - inet_addr(kMulticastAddress), - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_addr.s_addr); - // On the first iteration, save the port we are bound to. On the second - // iteration, verify the port is the same as the one from the first - // iteration. In other words, both sockets listen on the same port. - if (bound_port == 0) { - bound_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - } else { - EXPECT_EQ( - inet_addr(kMulticastAddress), - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_addr.s_addr); - EXPECT_EQ(bound_port, - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port); - } - - // Register to receive multicast packets. - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - } - - // Send a multicast packet to the group and verify both receivers get it. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - for (auto& receiver : receivers) { - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } -} - -// Check that two sockets can join the same multicast group at the same time, -// and with one bound to the wildcard address and the other bound to the -// multicast address, both will receive data. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - TestSendMulticastToTwoBoundToAnyAndMulticastAddress) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - std::unique_ptr<FileDescriptor> receivers[2] = { - ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), - ASSERT_NO_ERRNO_AND_VALUE(NewSocket())}; - - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - // The first receiver binds to the wildcard address. - auto receiver_addr = V4Any(); - int bound_port = 0; - for (auto& receiver : receivers) { - ASSERT_THAT(setsockopt(receiver->get(), SOL_SOCKET, SO_REUSEPORT, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceeds()); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - // On the first iteration, save the port we are bound to and change the - // receiver address from V4Any to V4Multicast so the second receiver binds - // to that. On the second iteration, verify the port is the same as the one - // from the first iteration but the address is different. - if (bound_port == 0) { - EXPECT_EQ( - htonl(INADDR_ANY), - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_addr.s_addr); - bound_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - receiver_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port = - bound_port; - } else { - EXPECT_EQ( - inet_addr(kMulticastAddress), - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_addr.s_addr); - EXPECT_EQ(bound_port, - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port); - } - - // Register to receive multicast packets. - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, - &group, sizeof(group)), - SyscallSucceeds()); - } - - // Send a multicast packet to the group and verify both receivers get it. - auto send_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - for (auto& receiver : receivers) { - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT( - RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - } -} - -// Check that when receiving a looped-back multicast packet, its source address -// is not a multicast address. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - IpMulticastLoopbackFromAddr) { - SKIP_IF(!found_net_interfaces_); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - int receiver_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Connect to the multicast address. This binds us to the outgoing interface - // and allows us to get its IP (to be compared against the src-IP on the - // receiver side). - auto sendto_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port; - ASSERT_THAT(RetryEINTR(connect)( - sender->get(), reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceeds()); - auto sender_addr = V4EmptyAddress(); - ASSERT_THAT( - getsockname(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - &sender_addr.addr_len), - SyscallSucceeds()); - ASSERT_EQ(sizeof(struct sockaddr_in), sender_addr.addr_len); - sockaddr_in* sender_addr_in = - reinterpret_cast<sockaddr_in*>(&sender_addr.addr); - - // Send a multicast packet. - char send_buf[4] = {}; - ASSERT_THAT(RetryEINTR(send)(sender->get(), send_buf, sizeof(send_buf), 0), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Receive a multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - auto src_addr = V4EmptyAddress(); - ASSERT_THAT( - RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0, - reinterpret_cast<sockaddr*>(&src_addr.addr), - &src_addr.addr_len), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len); - sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr); - - // Verify that the received source IP:port matches the sender one. - EXPECT_EQ(sender_addr_in->sin_port, src_addr_in->sin_port); - EXPECT_EQ(sender_addr_in->sin_addr.s_addr, src_addr_in->sin_addr.s_addr); -} - -// Check that when setting the IP_MULTICAST_IF option to both an index pointing -// to the loopback interface and an address pointing to the non-loopback -// interface, a multicast packet sent out uses the latter as its source address. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - IpMulticastLoopbackIfNicAndAddr) { - SKIP_IF(!found_net_interfaces_); - - // Create receiver, bind to ANY and join the multicast group. - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver_addr = V4Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - int receiver_port = - reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = lo_if_idx_; - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallSucceeds()); - - // Set outgoing multicast interface config, with NIC and addr pointing to - // different interfaces. - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - ip_mreqn iface = {}; - iface.imr_ifindex = lo_if_idx_; - iface.imr_address = eth_if_addr_.sin_addr; - ASSERT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); - - // Send a multicast packet. - auto sendto_addr = V4Multicast(); - reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port; - char send_buf[4] = {}; - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr.addr), - sendto_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Receive a multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - auto src_addr = V4EmptyAddress(); - ASSERT_THAT( - RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0, - reinterpret_cast<sockaddr*>(&src_addr.addr), - &src_addr.addr_len), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len); - sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr); - - // FIXME (b/137781162): When sending a multicast packet use the proper logic - // to determine the packet's src-IP. - SKIP_IF(IsRunningOnGvisor()); - - // Verify the received source address. - EXPECT_EQ(eth_if_addr_.sin_addr.s_addr, src_addr_in->sin_addr.s_addr); -} - -// Check that when we are bound to one interface we can set IP_MULTICAST_IF to -// another interface. -TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, - IpMulticastLoopbackBindToOneIfSetMcastIfToAnother) { - SKIP_IF(!found_net_interfaces_); - - // FIXME (b/137790511): When bound to one interface it is not possible to set - // IP_MULTICAST_IF to a different interface. - SKIP_IF(IsRunningOnGvisor()); - - // Create sender and bind to eth interface. - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - ASSERT_THAT(bind(sender->get(), reinterpret_cast<sockaddr*>(ð_if_addr_), - sizeof(eth_if_addr_)), - SyscallSucceeds()); - - // Run through all possible combinations of index and address for - // IP_MULTICAST_IF that selects the loopback interface. - struct { - int imr_ifindex; - struct in_addr imr_address; - } test_data[] = { - {lo_if_idx_, {}}, - {0, lo_if_addr_.sin_addr}, - {lo_if_idx_, lo_if_addr_.sin_addr}, - {lo_if_idx_, eth_if_addr_.sin_addr}, - }; - for (auto t : test_data) { - ip_mreqn iface = {}; - iface.imr_ifindex = t.imr_ifindex; - iface.imr_address = t.imr_address; - EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()) - << "imr_index=" << iface.imr_ifindex - << " imr_address=" << GetAddr4Str(&iface.imr_address); - } -} -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h deleted file mode 100644 index 20922ac1f..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ - -#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox -// with external networking support. -using IPv4UDPUnboundExternalNetworkingSocketTest = - IPUDPUnboundExternalNetworkingSocketTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc deleted file mode 100644 index f6e64c157..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketKind> GetSockets() { - return ApplyVec<SocketKind>( - IPv4UDPUnboundSocket, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P(IPv4UDPUnboundSockets, - IPv4UDPUnboundExternalNetworkingSocketTest, - ::testing::ValuesIn(GetSockets())); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc deleted file mode 100644 index f121c044d..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_ipv4_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( - IPv4UDPSockets, IPv4UDPUnboundSocketTest, - ::testing::ValuesIn(ApplyVec<SocketKind>(IPv4UDPUnboundSocket, - AllBitwiseCombinations(List<int>{ - 0, SOCK_NONBLOCK})))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc deleted file mode 100644 index 8052bf404..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc +++ /dev/null @@ -1,32 +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. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -INSTANTIATE_TEST_SUITE_P( - IPv4UDPSockets, IPv4UDPUnboundSocketNetlinkTest, - ::testing::ValuesIn(ApplyVec<SocketKind>(IPv4UDPUnboundSocket, - AllBitwiseCombinations(List<int>{ - 0, SOCK_NONBLOCK})))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc deleted file mode 100644 index bcbd2feac..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_nogotsan.cc +++ /dev/null @@ -1,94 +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. - -#include <sys/socket.h> -#include <sys/types.h> - -#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/test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to IPv4 UDP sockets. -using IPv4UDPUnboundSocketNogotsanTest = SimpleSocketTest; - -// Check that connect returns EAGAIN when out of local ephemeral ports. -// We disable S/R because this test creates a large number of sockets. -TEST_P(IPv4UDPUnboundSocketNogotsanTest, - UDPConnectPortExhaustion_NoRandomSave) { - auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - constexpr int kClients = 65536; - // Bind the first socket to the loopback and take note of the selected port. - auto addr = V4Loopback(); - ASSERT_THAT(bind(receiver1->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len), - SyscallSucceeds()); - socklen_t addr_len = addr.addr_len; - ASSERT_THAT(getsockname(receiver1->get(), - reinterpret_cast<sockaddr*>(&addr.addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, addr.addr_len); - - // Disable cooperative S/R as we are making too many syscalls. - DisableSave ds; - std::vector<std::unique_ptr<FileDescriptor>> sockets; - for (int i = 0; i < kClients; i++) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int ret = connect(s->get(), reinterpret_cast<sockaddr*>(&addr.addr), - addr.addr_len); - if (ret == 0) { - sockets.push_back(std::move(s)); - continue; - } - ASSERT_THAT(ret, SyscallFailsWithErrno(EAGAIN)); - break; - } -} - -// Check that bind returns EADDRINUSE when out of local ephemeral ports. -// We disable S/R because this test creates a large number of sockets. -TEST_P(IPv4UDPUnboundSocketNogotsanTest, UDPBindPortExhaustion_NoRandomSave) { - auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - constexpr int kClients = 65536; - auto addr = V4Loopback(); - // Disable cooperative S/R as we are making too many syscalls. - DisableSave ds; - std::vector<std::unique_ptr<FileDescriptor>> sockets; - for (int i = 0; i < kClients; i++) { - auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - int ret = - bind(s->get(), reinterpret_cast<sockaddr*>(&addr.addr), addr.addr_len); - if (ret == 0) { - sockets.push_back(std::move(s)); - continue; - } - ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRINUSE)); - break; - } -} - -INSTANTIATE_TEST_SUITE_P( - IPv4UDPSockets, IPv4UDPUnboundSocketNogotsanTest, - ::testing::ValuesIn(ApplyVec<SocketKind>(IPv4UDPUnboundSocket, - AllBitwiseCombinations(List<int>{ - 0, SOCK_NONBLOCK})))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc deleted file mode 100644 index 9a9ddc297..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc +++ /dev/null @@ -1,209 +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. - -#include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h" - -#include <arpa/inet.h> -#include <poll.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_netlink_route_util.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" - -namespace gvisor { -namespace testing { - -constexpr size_t kSendBufSize = 200; - -// Checks that the loopback interface considers itself bound to all IPs in an -// associated subnet. -TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // Add an IP address to the loopback interface. - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - struct in_addr addr; - ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr)); - ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr))); - Cleanup defer_addr_removal = Cleanup( - [loopback_link = std::move(loopback_link), addr = std::move(addr)] { - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, - sizeof(addr))); - }); - - auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto rcv_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Send from an unassigned address but an address that is in the subnet - // associated with the loopback interface. - TestAddress sender_addr("V4NotAssignd1"); - sender_addr.addr.ss_family = AF_INET; - sender_addr.addr_len = sizeof(sockaddr_in); - ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.2", - &(reinterpret_cast<sockaddr_in*>(&sender_addr.addr) - ->sin_addr.s_addr))); - ASSERT_THAT( - bind(snd_sock->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Send the packet to an unassigned address but an address that is in the - // subnet associated with the loopback interface. - TestAddress receiver_addr("V4NotAssigned2"); - receiver_addr.addr.ss_family = AF_INET; - receiver_addr.addr_len = sizeof(sockaddr_in); - ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.254", - &(reinterpret_cast<sockaddr_in*>(&receiver_addr.addr) - ->sin_addr.s_addr))); - ASSERT_THAT( - bind(rcv_sock->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - socklen_t receiver_addr_len = receiver_addr.addr_len; - ASSERT_THAT(getsockname(rcv_sock->get(), - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - &receiver_addr_len), - SyscallSucceeds()); - ASSERT_EQ(receiver_addr_len, receiver_addr.addr_len); - char send_buf[kSendBufSize]; - RandomizeBuffer(send_buf, kSendBufSize); - ASSERT_THAT( - RetryEINTR(sendto)(snd_sock->get(), send_buf, kSendBufSize, 0, - reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceedsWithValue(kSendBufSize)); - - // Check that we received the packet. - char recv_buf[kSendBufSize] = {}; - ASSERT_THAT(RetryEINTR(recv)(rcv_sock->get(), recv_buf, kSendBufSize, 0), - SyscallSucceedsWithValue(kSendBufSize)); - ASSERT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize)); -} - -// Tests that broadcast packets are delivered to all interested sockets -// (wildcard and broadcast address specified sockets). -// -// Note, we cannot test the IPv4 Broadcast (255.255.255.255) because we do -// not have a route to it. -TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { - constexpr uint16_t kPort = 9876; - // Wait up to 20 seconds for the data. - constexpr int kPollTimeoutMs = 20000; - // Number of sockets per socket type. - constexpr int kNumSocketsPerType = 2; - - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // Add an IP address to the loopback interface. - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - struct in_addr addr; - ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr)); - ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, - 24 /* prefixlen */, &addr, sizeof(addr))); - Cleanup defer_addr_removal = Cleanup( - [loopback_link = std::move(loopback_link), addr = std::move(addr)] { - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, - sizeof(addr))); - }); - - TestAddress broadcast_address("SubnetBroadcastAddress"); - broadcast_address.addr.ss_family = AF_INET; - broadcast_address.addr_len = sizeof(sockaddr_in); - auto broadcast_address_in = - reinterpret_cast<sockaddr_in*>(&broadcast_address.addr); - ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.255", - &broadcast_address_in->sin_addr.s_addr)); - broadcast_address_in->sin_port = htons(kPort); - - TestAddress any_address = V4Any(); - reinterpret_cast<sockaddr_in*>(&any_address.addr)->sin_port = htons(kPort); - - // We create sockets bound to both the wildcard address and the broadcast - // address to make sure both of these types of "broadcast interested" sockets - // receive broadcast packets. - std::vector<std::unique_ptr<FileDescriptor>> socks; - for (bool bind_wildcard : {false, true}) { - // Create multiple sockets for each type of "broadcast interested" - // socket so we can test that all sockets receive the broadcast packet. - for (int i = 0; i < kNumSocketsPerType; i++) { - auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto idx = socks.size(); - - ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)) - << "socks[" << idx << "]"; - - ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)) - << "socks[" << idx << "]"; - - if (bind_wildcard) { - ASSERT_THAT( - bind(sock->get(), reinterpret_cast<sockaddr*>(&any_address.addr), - any_address.addr_len), - SyscallSucceeds()) - << "socks[" << idx << "]"; - } else { - ASSERT_THAT(bind(sock->get(), - reinterpret_cast<sockaddr*>(&broadcast_address.addr), - broadcast_address.addr_len), - SyscallSucceeds()) - << "socks[" << idx << "]"; - } - - socks.push_back(std::move(sock)); - } - } - - char send_buf[kSendBufSize]; - RandomizeBuffer(send_buf, kSendBufSize); - - // Broadcasts from each socket should be received by every socket (including - // the sending socket). - 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, - reinterpret_cast<sockaddr*>(&broadcast_address.addr), - broadcast_address.addr_len), - SyscallSucceedsWithValue(kSendBufSize)) - << "write socks[" << w << "]"; - - // Check that we received the packet on all sockets. - for (long unsigned int r = 0; r < socks.size(); r++) { - auto& r_sock = socks[r]; - - struct pollfd poll_fd = {r_sock->get(), POLLIN, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)) - << "write socks[" << w << "] & read socks[" << r << "]"; - - char recv_buf[kSendBufSize] = {}; - EXPECT_THAT(RetryEINTR(recv)(r_sock->get(), recv_buf, kSendBufSize, 0), - SyscallSucceedsWithValue(kSendBufSize)) - << "write socks[" << w << "] & read socks[" << r << "]"; - EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize)) - << "write socks[" << w << "] & read socks[" << r << "]"; - } - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h deleted file mode 100644 index 73e7836d5..000000000 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h +++ /dev/null @@ -1,29 +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. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to IPv4 UDP sockets. -using IPv4UDPUnboundSocketNetlinkTest = SimpleSocketTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc deleted file mode 100644 index 08526468e..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound.cc +++ /dev/null @@ -1,131 +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. - -#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 deleted file mode 100644 index 71e160f73..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound.h +++ /dev/null @@ -1,29 +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. - -#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_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc deleted file mode 100644 index 7364a1ea5..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc +++ /dev/null @@ -1,90 +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. - -#include "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" - -namespace gvisor { -namespace testing { - -TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) { - SKIP_IF(!found_net_interfaces_); - - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - auto receiver_addr = V6Any(); - ASSERT_THAT( - bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), - receiver_addr.addr_len), - SyscallSucceeds()); - 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); - - // Register to receive multicast packets. - auto multicast_addr = V6Multicast(); - ipv6_mreq group_req = { - .ipv6mr_multiaddr = - reinterpret_cast<sockaddr_in6*>(&multicast_addr.addr)->sin6_addr, - .ipv6mr_interface = - (unsigned int)ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")), - }; - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, - &group_req, sizeof(group_req)), - SyscallSucceeds()); - - // Set the sender to the loopback interface. - auto sender_addr = V6Loopback(); - ASSERT_THAT( - bind(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallSucceeds()); - - // Send a multicast packet. - auto send_addr = multicast_addr; - reinterpret_cast<sockaddr_in6*>(&send_addr.addr)->sin6_port = - reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr)->sin6_port; - char send_buf[200]; - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Check that we received the multicast packet. - char recv_buf[sizeof(send_buf)] = {}; - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - - EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); - - // Leave the group and make sure we don't receive its multicast traffic. - ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, - &group_req, sizeof(group_req)), - SyscallSucceeds()); - RandomizeBuffer(send_buf, sizeof(send_buf)); - ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, - reinterpret_cast<sockaddr*>(&send_addr.addr), - send_addr.addr_len), - SyscallSucceedsWithValue(sizeof(send_buf))); - ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), - MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h deleted file mode 100644 index 731ae0a1f..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ - -#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to unbound IPv6 UDP sockets in a sandbox -// with external networking support. -using IPv6UDPUnboundExternalNetworkingSocketTest = - IPUDPUnboundExternalNetworkingSocketTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6yy_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc deleted file mode 100644 index 5c764b8fd..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc +++ /dev/null @@ -1,39 +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. - -#include "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketKind> GetSockets() { - return ApplyVec<SocketKind>( - IPv6UDPUnboundSocket, - AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P(IPv6UDPUnboundSockets, - IPv6UDPUnboundExternalNetworkingSocketTest, - ::testing::ValuesIn(GetSockets())); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc deleted file mode 100644 index 058336ecc..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc +++ /dev/null @@ -1,32 +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. - -#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_ipv6_udp_unbound_loopback_netlink.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc deleted file mode 100644 index 17021ff82..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc +++ /dev/null @@ -1,32 +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. - -#include <vector> - -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -INSTANTIATE_TEST_SUITE_P( - IPv6UDPSockets, IPv6UDPUnboundSocketNetlinkTest, - ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket, - AllBitwiseCombinations(List<int>{ - 0, SOCK_NONBLOCK})))); - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc deleted file mode 100644 index 2ee218231..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc +++ /dev/null @@ -1,53 +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. - -#include "test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h" - -#include <arpa/inet.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_netlink_route_util.h" -#include "test/util/capability_util.h" - -namespace gvisor { -namespace testing { - -// Checks that the loopback interface does not consider itself bound to all IPs -// in an associated subnet. -TEST_P(IPv6UDPUnboundSocketNetlinkTest, JoinSubnet) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // Add an IP address to the loopback interface. - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - struct in6_addr addr; - EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::1", &addr)); - EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6, - /*prefixlen=*/64, &addr, sizeof(addr))); - - // Binding to an unassigned address but an address that is in the subnet - // associated with the loopback interface should fail. - TestAddress sender_addr("V6NotAssignd1"); - sender_addr.addr.ss_family = AF_INET6; - sender_addr.addr_len = sizeof(sockaddr_in6); - EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::2", - reinterpret_cast<sockaddr_in6*>(&sender_addr.addr) - ->sin6_addr.s6_addr)); - auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - EXPECT_THAT(bind(sock->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), - sender_addr.addr_len), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h deleted file mode 100644 index 88098be82..000000000 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h +++ /dev/null @@ -1,29 +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. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to IPv6 UDP sockets. -using IPv6UDPUnboundSocketNetlinkTest = SimpleSocketTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_ diff --git a/test/syscalls/linux/socket_netdevice.cc b/test/syscalls/linux/socket_netdevice.cc deleted file mode 100644 index 5f8d7f981..000000000 --- a/test/syscalls/linux/socket_netdevice.cc +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/ethtool.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> -#include <linux/sockios.h> -#include <sys/ioctl.h> -#include <sys/socket.h> - -#include "gtest/gtest.h" -#include "absl/base/internal/endian.h" -#include "test/syscalls/linux/socket_netlink_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Tests for netdevice queries. - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::AnyOf; -using ::testing::Eq; - -TEST(NetdeviceTest, Loopback) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - // Prepare the request. - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - - // Check for a non-zero interface index. - ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - - // Check that the loopback is zero hardware address. - ASSERT_THAT(ioctl(sock.get(), SIOCGIFHWADDR, &ifr), SyscallSucceeds()); - EXPECT_EQ(ifr.ifr_hwaddr.sa_family, ARPHRD_LOOPBACK); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[0], 0); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[1], 0); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[2], 0); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[3], 0); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[4], 0); - EXPECT_EQ(ifr.ifr_hwaddr.sa_data[5], 0); -} - -TEST(NetdeviceTest, Netmask) { - // We need an interface index to identify the loopback device. - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - - // Use a netlink socket to get the netmask, which we'll then compare to the - // netmask obtained via ioctl. - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - constexpr uint32_t kSeq = 12345; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - // Iterate through messages until we find the one containing the prefix length - // (i.e. netmask) for the loopback device. - int prefixlen = -1; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWADDR), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - if (hdr->nlmsg_type != RTM_NEWADDR) { - return; - } - - // RTM_NEWADDR contains at least the header and ifaddrmsg. - EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct ifaddrmsg)); - - struct ifaddrmsg* ifaddrmsg = - reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(hdr)); - if (ifaddrmsg->ifa_index == static_cast<uint32_t>(ifr.ifr_ifindex) && - ifaddrmsg->ifa_family == AF_INET) { - prefixlen = ifaddrmsg->ifa_prefixlen; - } - }, - false)); - - ASSERT_GE(prefixlen, 0); - - // Netmask is stored big endian in struct sockaddr_in, so we do the same for - // comparison. - uint32_t mask = 0xffffffff << (32 - prefixlen); - mask = absl::gbswap_32(mask); - - // Check that the loopback interface has the correct subnet mask. - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(sock.get(), SIOCGIFNETMASK, &ifr), SyscallSucceeds()); - EXPECT_EQ(ifr.ifr_netmask.sa_family, AF_INET); - struct sockaddr_in* sin = - reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_netmask); - EXPECT_EQ(sin->sin_addr.s_addr, mask); -} - -TEST(NetdeviceTest, InterfaceName) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - // Prepare the request. - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - - // Check for a non-zero interface index. - ASSERT_THAT(ioctl(sock.get(), SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - - // Check that SIOCGIFNAME finds the loopback interface. - snprintf(ifr.ifr_name, IFNAMSIZ, "foo"); - ASSERT_THAT(ioctl(sock.get(), SIOCGIFNAME, &ifr), SyscallSucceeds()); - EXPECT_STREQ(ifr.ifr_name, "lo"); -} - -TEST(NetdeviceTest, InterfaceFlags) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - // Prepare the request. - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - - // Check that SIOCGIFFLAGS marks the interface with IFF_LOOPBACK, IFF_UP, and - // IFF_RUNNING. - ASSERT_THAT(ioctl(sock.get(), SIOCGIFFLAGS, &ifr), SyscallSucceeds()); - EXPECT_EQ(ifr.ifr_flags & IFF_UP, IFF_UP); - EXPECT_EQ(ifr.ifr_flags & IFF_RUNNING, IFF_RUNNING); -} - -TEST(NetdeviceTest, InterfaceMTU) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - // Prepare the request. - struct ifreq ifr = {}; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - - // Check that SIOCGIFMTU returns a nonzero MTU. - ASSERT_THAT(ioctl(sock.get(), SIOCGIFMTU, &ifr), SyscallSucceeds()); - EXPECT_GT(ifr.ifr_mtu, 0); -} - -TEST(NetdeviceTest, EthtoolGetTSInfo) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct ethtool_ts_info tsi = {}; - tsi.cmd = ETHTOOL_GET_TS_INFO; // Get NIC's Timestamping capabilities. - - // Prepare the request. - struct ifreq ifr = {}; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - ifr.ifr_data = (void*)&tsi; - - // Check that SIOCGIFMTU returns a nonzero MTU. - if (IsRunningOnGvisor()) { - ASSERT_THAT(ioctl(sock.get(), SIOCETHTOOL, &ifr), - SyscallFailsWithErrno(EOPNOTSUPP)); - return; - } - ASSERT_THAT(ioctl(sock.get(), SIOCETHTOOL, &ifr), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink.cc b/test/syscalls/linux/socket_netlink.cc deleted file mode 100644 index 4ec0fd4fa..000000000 --- a/test/syscalls/linux/socket_netlink.cc +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/netlink.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Tests for all netlink socket protocols. - -namespace gvisor { -namespace testing { - -namespace { - -// NetlinkTest parameter is the protocol to test. -using NetlinkTest = ::testing::TestWithParam<int>; - -// Netlink sockets must be SOCK_DGRAM or SOCK_RAW. -TEST_P(NetlinkTest, Types) { - const int protocol = GetParam(); - - EXPECT_THAT(socket(AF_NETLINK, SOCK_STREAM, protocol), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - EXPECT_THAT(socket(AF_NETLINK, SOCK_SEQPACKET, protocol), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - EXPECT_THAT(socket(AF_NETLINK, SOCK_RDM, protocol), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - EXPECT_THAT(socket(AF_NETLINK, SOCK_DCCP, protocol), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - EXPECT_THAT(socket(AF_NETLINK, SOCK_PACKET, protocol), - SyscallFailsWithErrno(ESOCKTNOSUPPORT)); - - int fd; - EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_DGRAM, protocol), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_RAW, protocol), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_P(NetlinkTest, AutomaticPort) { - const int protocol = GetParam(); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol)); - - struct sockaddr_nl addr = {}; - addr.nl_family = AF_NETLINK; - - EXPECT_THAT( - bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceeds()); - - socklen_t addrlen = sizeof(addr); - EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, sizeof(addr)); - // This is the only netlink socket in the process, so it should get the PID as - // the port id. - // - // N.B. Another process could theoretically have explicitly reserved our pid - // as a port ID, but that is very unlikely. - EXPECT_EQ(addr.nl_pid, getpid()); -} - -// Calling connect automatically binds to an automatic port. -TEST_P(NetlinkTest, ConnectBinds) { - const int protocol = GetParam(); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol)); - - struct sockaddr_nl addr = {}; - addr.nl_family = AF_NETLINK; - - EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - socklen_t addrlen = sizeof(addr); - EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, sizeof(addr)); - - // Each test is running in a pid namespace, so another process can explicitly - // reserve our pid as a port ID. In this case, a negative portid value will be - // set. - if (static_cast<pid_t>(addr.nl_pid) > 0) { - EXPECT_EQ(addr.nl_pid, getpid()); - } - - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - - // Connecting again is allowed, but keeps the same port. - EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - sizeof(addr)), - SyscallSucceeds()); - - addrlen = sizeof(addr); - EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, sizeof(addr)); - EXPECT_EQ(addr.nl_pid, getpid()); -} - -TEST_P(NetlinkTest, GetPeerName) { - const int protocol = GetParam(); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol)); - - struct sockaddr_nl addr = {}; - socklen_t addrlen = sizeof(addr); - - EXPECT_THAT(getpeername(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), - &addrlen), - SyscallSucceeds()); - - EXPECT_EQ(addrlen, sizeof(addr)); - EXPECT_EQ(addr.nl_family, AF_NETLINK); - // Peer is the kernel if we didn't connect elsewhere. - EXPECT_EQ(addr.nl_pid, 0); -} - -INSTANTIATE_TEST_SUITE_P(ProtocolTest, NetlinkTest, - ::testing::Values(NETLINK_ROUTE, - NETLINK_KOBJECT_UEVENT)); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc deleted file mode 100644 index ee3c08770..000000000 --- a/test/syscalls/linux/socket_netlink_route.cc +++ /dev/null @@ -1,971 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <fcntl.h> -#include <ifaddrs.h> -#include <linux/if.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <iostream> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "test/syscalls/linux/socket_netlink_route_util.h" -#include "test/syscalls/linux/socket_netlink_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Tests for NETLINK_ROUTE sockets. - -namespace gvisor { -namespace testing { - -namespace { - -constexpr uint32_t kSeq = 12345; - -using ::testing::AnyOf; -using ::testing::Eq; - -// Parameters for SockOptTest. They are: -// 0: Socket option to query. -// 1: A predicate to run on the returned sockopt value. Should return true if -// the value is considered ok. -// 2: A description of what the sockopt value is expected to be. Should complete -// the sentence "<value> was unexpected, expected <description>" -using SockOptTest = ::testing::TestWithParam< - std::tuple<int, std::function<bool(int)>, std::string>>; - -TEST_P(SockOptTest, GetSockOpt) { - int sockopt = std::get<0>(GetParam()); - auto verifier = std::get<1>(GetParam()); - std::string verifier_description = std::get<2>(GetParam()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); - - int res; - socklen_t len = sizeof(res); - - EXPECT_THAT(getsockopt(fd.get(), SOL_SOCKET, sockopt, &res, &len), - SyscallSucceeds()); - - EXPECT_EQ(len, sizeof(res)); - EXPECT_TRUE(verifier(res)) << absl::StrFormat( - "getsockopt(%d, SOL_SOCKET, %d, &res, &len) => res=%d was unexpected, " - "expected %s", - fd.get(), sockopt, res, verifier_description); -} - -std::function<bool(int)> IsPositive() { - return [](int val) { return val > 0; }; -} - -std::function<bool(int)> IsEqual(int target) { - return [target](int val) { return val == target; }; -} - -INSTANTIATE_TEST_SUITE_P( - NetlinkRouteTest, SockOptTest, - ::testing::Values( - std::make_tuple(SO_SNDBUF, IsPositive(), "positive send buffer size"), - std::make_tuple(SO_RCVBUF, IsPositive(), - "positive receive buffer size"), - std::make_tuple(SO_TYPE, IsEqual(SOCK_RAW), - absl::StrFormat("SOCK_RAW (%d)", SOCK_RAW)), - std::make_tuple(SO_DOMAIN, IsEqual(AF_NETLINK), - absl::StrFormat("AF_NETLINK (%d)", AF_NETLINK)), - std::make_tuple(SO_PROTOCOL, IsEqual(NETLINK_ROUTE), - absl::StrFormat("NETLINK_ROUTE (%d)", NETLINK_ROUTE)), - std::make_tuple(SO_PASSCRED, IsEqual(0), "0"))); - -// Validates the reponses to RTM_GETLINK + NLM_F_DUMP. -void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) { - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWLINK), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, seq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - if (hdr->nlmsg_type != RTM_NEWLINK) { - return; - } - - // RTM_NEWLINK contains at least the header and ifinfomsg. - EXPECT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - - // TODO(mpratt): Check ifinfomsg contents and following attrs. -} - -TEST(NetlinkRouteTest, GetLinkDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - // Loopback is common among all tests, check that it's found. - bool loopbackFound = false; - ASSERT_NO_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) { - CheckGetLinkResponse(hdr, kSeq, port); - if (hdr->nlmsg_type != RTM_NEWLINK) { - return; - } - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - std::cout << "Found interface idx=" << msg->ifi_index - << ", type=" << std::hex << msg->ifi_type << std::endl; - if (msg->ifi_type == ARPHRD_LOOPBACK) { - loopbackFound = true; - EXPECT_NE(msg->ifi_flags & IFF_LOOPBACK, 0); - } - })); - EXPECT_TRUE(loopbackFound); -} - -// CheckLinkMsg checks a netlink message against an expected link. -void CheckLinkMsg(const struct nlmsghdr* hdr, const Link& link) { - ASSERT_THAT(hdr->nlmsg_type, Eq(RTM_NEWLINK)); - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - EXPECT_EQ(msg->ifi_index, link.index); - - const struct rtattr* rta = FindRtAttr(hdr, msg, IFLA_IFNAME); - EXPECT_NE(nullptr, rta) << "IFLA_IFNAME not found in message."; - if (rta != nullptr) { - std::string name(reinterpret_cast<const char*>(RTA_DATA(rta))); - EXPECT_EQ(name, link.name); - } -} - -TEST(NetlinkRouteTest, GetLinkByIndex) { - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.ifm.ifi_index = loopback_link.index; - - bool found = false; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckLinkMsg(hdr, loopback_link); - found = true; - }, - false)); - EXPECT_TRUE(found) << "Netlink response does not contain any links."; -} - -TEST(NetlinkRouteTest, GetLinkByName) { - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - struct rtattr rtattr; - char ifname[IFNAMSIZ]; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.rtattr.rta_type = IFLA_IFNAME; - req.rtattr.rta_len = RTA_LENGTH(loopback_link.name.size() + 1); - strncpy(req.ifname, loopback_link.name.c_str(), sizeof(req.ifname)); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); - - bool found = false; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckLinkMsg(hdr, loopback_link); - found = true; - }, - false)); - EXPECT_TRUE(found) << "Netlink response does not contain any links."; -} - -TEST(NetlinkRouteTest, GetLinkByIndexNotFound) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.ifm.ifi_index = 1234590; - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(ENODEV, ::testing::_)); -} - -TEST(NetlinkRouteTest, GetLinkByNameNotFound) { - const std::string name = "nodevice?!"; - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - struct rtattr rtattr; - char ifname[IFNAMSIZ]; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.rtattr.rta_type = IFLA_IFNAME; - req.rtattr.rta_len = RTA_LENGTH(name.size() + 1); - strncpy(req.ifname, name.c_str(), sizeof(req.ifname)); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(ENODEV, ::testing::_)); -} - -TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - // If type & 0x3 is equal to 0x2, this means a get request - // which doesn't require CAP_SYS_ADMIN. - req.hdr.nlmsg_type = ((__RTM_MAX + 1024) & (~0x3)) | 0x2; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(EOPNOTSUPP, ::testing::_)); -} - -TEST(NetlinkRouteTest, MsgHdrMsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // Small enough to ensure that the response doesn't fit. - constexpr size_t kBufferSize = 10; - std::vector<char> buf(kBufferSize); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), - SyscallSucceedsWithValue(kBufferSize)); - EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); -} - -TEST(NetlinkRouteTest, SpliceFromPipe) { - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.ifm.ifi_index = loopback_link.index; - - ASSERT_THAT(write(wfd.get(), &req, sizeof(req)), - SyscallSucceedsWithValue(sizeof(req))); - - EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), - SyscallSucceedsWithValue(sizeof(req))); - close(wfd.release()); - EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), - SyscallSucceedsWithValue(0)); - - bool found = false; - ASSERT_NO_ERRNO(NetlinkResponse( - fd, - [&](const struct nlmsghdr* hdr) { - CheckLinkMsg(hdr, loopback_link); - found = true; - }, - false)); - EXPECT_TRUE(found) << "Netlink response does not contain any links."; -} - -TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // Small enough to ensure that the response doesn't fit. - constexpr size_t kBufferSize = 10; - std::vector<char> buf(kBufferSize); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - int res = 0; - ASSERT_THAT(res = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - EXPECT_GT(res, kBufferSize); - EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); -} - -TEST(NetlinkRouteTest, ControlMessageIgnored) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr control_hdr; - struct nlmsghdr message_hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - - // This control message is ignored. We still receive a response for the - // following RTM_GETLINK. - req.control_hdr.nlmsg_len = sizeof(req.control_hdr); - req.control_hdr.nlmsg_type = NLMSG_DONE; - req.control_hdr.nlmsg_seq = kSeq; - - req.message_hdr.nlmsg_len = sizeof(req.message_hdr) + sizeof(req.ifm); - req.message_hdr.nlmsg_type = RTM_GETLINK; - req.message_hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.message_hdr.nlmsg_seq = kSeq; - - req.ifm.ifi_family = AF_UNSPEC; - - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckGetLinkResponse(hdr, kSeq, port); - }, - false)); -} - -TEST(NetlinkRouteTest, GetAddrDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWADDR), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - if (hdr->nlmsg_type != RTM_NEWADDR) { - return; - } - - // RTM_NEWADDR contains at least the header and ifaddrmsg. - EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct ifaddrmsg)); - - // TODO(mpratt): Check ifaddrmsg contents and following attrs. - }, - false)); -} - -TEST(NetlinkRouteTest, LookupAll) { - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - // Not a syscall but we can use the syscall matcher as glibc sets errno. - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - int count = 0; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (!i->ifa_addr || (i->ifa_addr->sa_family != AF_INET && - i->ifa_addr->sa_family != AF_INET6)) { - continue; - } - count++; - } - ASSERT_GT(count, 0); -} - -TEST(NetlinkRouteTest, AddAndRemoveAddr) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - // Don't do cooperative save/restore because netstack state is not restored. - // TODO(gvisor.dev/issue/4595): enable cooperative save tests. - const DisableSave ds; - - Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); - - struct in_addr addr; - ASSERT_EQ(inet_pton(AF_INET, "10.0.0.1", &addr), 1); - - // Create should succeed, as no such address in kernel. - ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr))); - - Cleanup defer_addr_removal = Cleanup( - [loopback_link = std::move(loopback_link), addr = std::move(addr)] { - // First delete should succeed, as address exists. - EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, - sizeof(addr))); - - // Second delete should fail, as address no longer exists. - EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr)), - PosixErrorIs(EADDRNOTAVAIL, ::testing::_)); - }); - - // Replace an existing address should succeed. - ASSERT_NO_ERRNO(LinkReplaceLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr))); - - // Create exclusive should fail, as we created the address above. - EXPECT_THAT(LinkAddExclusiveLocalAddr(loopback_link.index, AF_INET, - /*prefixlen=*/24, &addr, sizeof(addr)), - PosixErrorIs(EEXIST, ::testing::_)); -} - -// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request. -TEST(NetlinkRouteTest, GetRouteDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtmsg rtm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETROUTE; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rtm.rtm_family = AF_UNSPEC; - - bool routeFound = false; - bool dstFound = true; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - // Validate the reponse to RTM_GETROUTE + NLM_F_DUMP. - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWROUTE), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - // The test should not proceed if it's not a RTM_NEWROUTE message. - if (hdr->nlmsg_type != RTM_NEWROUTE) { - return; - } - - // RTM_NEWROUTE contains at least the header and rtmsg. - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg))); - const struct rtmsg* msg = - reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr)); - // NOTE: rtmsg fields are char fields. - std::cout << "Found route table=" << static_cast<int>(msg->rtm_table) - << ", protocol=" << static_cast<int>(msg->rtm_protocol) - << ", scope=" << static_cast<int>(msg->rtm_scope) - << ", type=" << static_cast<int>(msg->rtm_type); - - int len = RTM_PAYLOAD(hdr); - bool rtDstFound = false; - for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len); - attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_DST) { - char address[INET_ADDRSTRLEN] = {}; - inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address)); - std::cout << ", dst=" << address; - rtDstFound = true; - } - } - - std::cout << std::endl; - - // If the test is running in a new network namespace, it will have only - // the local route table. - if (msg->rtm_table == RT_TABLE_MAIN || - (!IsRunningOnGvisor() && msg->rtm_table == RT_TABLE_LOCAL)) { - routeFound = true; - dstFound = rtDstFound && dstFound; - } - }, - false)); - // At least one route found in main route table. - EXPECT_TRUE(routeFound); - // Found RTA_DST for each route in main table. - EXPECT_TRUE(dstFound); -} - -// GetRouteRequest tests a RTM_GETROUTE request with RTM_F_LOOKUP_TABLE flag. -TEST(NetlinkRouteTest, GetRouteRequest) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtmsg rtm; - struct nlattr nla; - struct in_addr sin_addr; - }; - - constexpr uint32_t kSeq = 12345; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETROUTE; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - - req.rtm.rtm_family = AF_INET; - req.rtm.rtm_dst_len = 32; - req.rtm.rtm_src_len = 0; - req.rtm.rtm_tos = 0; - req.rtm.rtm_table = RT_TABLE_UNSPEC; - req.rtm.rtm_protocol = RTPROT_UNSPEC; - req.rtm.rtm_scope = RT_SCOPE_UNIVERSE; - req.rtm.rtm_type = RTN_UNSPEC; - req.rtm.rtm_flags = RTM_F_LOOKUP_TABLE; - - req.nla.nla_len = 8; - req.nla.nla_type = RTA_DST; - inet_aton("127.0.0.2", &req.sin_addr); - - bool rtDstFound = false; - ASSERT_NO_ERRNO(NetlinkRequestResponseSingle( - fd, &req, sizeof(req), [&](const struct nlmsghdr* hdr) { - // Validate the reponse to RTM_GETROUTE request with RTM_F_LOOKUP_TABLE - // flag. - EXPECT_THAT(hdr->nlmsg_type, RTM_NEWROUTE); - - EXPECT_TRUE(hdr->nlmsg_flags == 0) << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - // RTM_NEWROUTE contains at least the header and rtmsg. - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg))); - const struct rtmsg* msg = - reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr)); - - // NOTE: rtmsg fields are char fields. - std::cout << "Found route table=" << static_cast<int>(msg->rtm_table) - << ", protocol=" << static_cast<int>(msg->rtm_protocol) - << ", scope=" << static_cast<int>(msg->rtm_scope) - << ", type=" << static_cast<int>(msg->rtm_type); - - EXPECT_EQ(msg->rtm_family, AF_INET); - EXPECT_EQ(msg->rtm_dst_len, 32); - EXPECT_TRUE((msg->rtm_flags & RTM_F_CLONED) == RTM_F_CLONED) - << std::hex << msg->rtm_flags; - - int len = RTM_PAYLOAD(hdr); - std::cout << ", len=" << len; - for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len); - attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_DST) { - char address[INET_ADDRSTRLEN] = {}; - inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address)); - std::cout << ", dst=" << address; - rtDstFound = true; - } else if (attr->rta_type == RTA_OIF) { - const char* oif = reinterpret_cast<const char*>(RTA_DATA(attr)); - std::cout << ", oif=" << oif; - } - } - - std::cout << std::endl; - })); - // Found RTA_DST for RTM_F_LOOKUP_TABLE. - EXPECT_TRUE(rtDstFound); -} - -// RecvmsgTrunc tests the recvmsg MSG_TRUNC flag with zero length output -// buffer. MSG_TRUNC with a zero length buffer should consume subsequent -// messages off the socket. -TEST(NetlinkRouteTest, RecvmsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - int trunclen, trunclen2; - - // Note: This test assumes at least two messages are returned by the - // RTM_GETADDR request. That means at least one RTM_NEWLINK message and one - // NLMSG_DONE message. We cannot read all the messages without blocking - // because we would need to read the message into a buffer and check the - // nlmsg_type for NLMSG_DONE. However, the test depends on reading into a - // zero-length buffer. - - // First, call recvmsg with MSG_TRUNC. This will read the full message from - // the socket and return it's full length. Subsequent calls to recvmsg will - // read the next messages from the socket. - ASSERT_THAT(trunclen = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - - // Message should always be truncated. However, While the destination iov is - // zero length, MSG_TRUNC returns the size of the next message so it should - // not be zero. - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(trunclen, 0); - // Returned length is at least the header and ifaddrmsg. - EXPECT_GE(trunclen, sizeof(struct nlmsghdr) + sizeof(struct ifaddrmsg)); - - // Reset the msg_flags to make sure that the recvmsg call is setting them - // properly. - msg.msg_flags = 0; - - // Make a second recvvmsg call to get the next message. - ASSERT_THAT(trunclen2 = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(trunclen2, 0); - - // Assert that the received messages are not the same. - // - // We are calling recvmsg with a zero length buffer so we have no way to - // inspect the messages to make sure they are not equal in value. The best - // we can do is to compare their lengths. - ASSERT_NE(trunclen, trunclen2); -} - -// RecvmsgTruncPeek tests recvmsg with the combination of the MSG_TRUNC and -// MSG_PEEK flags and a zero length output buffer. This is normally used to -// read the full length of the next message on the socket without consuming -// it, so a properly sized buffer can be allocated to store the message. This -// test tests that scenario. -TEST(NetlinkRouteTest, RecvmsgTruncPeek) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - int type = -1; - do { - int peeklen; - int len; - - iov.iov_base = NULL; - iov.iov_len = 0; - - // Call recvmsg with MSG_PEEK and MSG_TRUNC. This will peek at the message - // and return it's full length. - // See: MSG_TRUNC http://man7.org/linux/man-pages/man2/recv.2.html - ASSERT_THAT( - peeklen = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_PEEK | MSG_TRUNC), - SyscallSucceeds()); - - // Message should always be truncated. - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(peeklen, 0); - - // Reset the message flags for the next call. - msg.msg_flags = 0; - - // Make the actual call to recvmsg to get the actual data. We will use - // the length returned from the peek call for the allocated buffer size.. - std::vector<char> buf(peeklen); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - ASSERT_THAT(len = RetryEINTR(recvmsg)(fd.get(), &msg, 0), - SyscallSucceeds()); - - // Message should not be truncated since we allocated the correct buffer - // size. - EXPECT_NE(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - - // MSG_PEEK should have left data on the socket and the subsequent call - // with should have retrieved the same data. Both calls should have - // returned the message's full length so they should be equal. - ASSERT_NE(len, 0); - ASSERT_EQ(peeklen, len); - - for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data()); - NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { - type = hdr->nlmsg_type; - } - } while (type != NLMSG_DONE && type != NLMSG_ERROR); -} - -// No SCM_CREDENTIALS are received without SO_PASSCRED set. -TEST(NetlinkRouteTest, NoPasscredNoCreds) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOff, - sizeof(kSockOptOff)), - SyscallSucceeds()); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - char control[CMSG_SPACE(sizeof(struct ucred))] = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - // Note: This test assumes at least one message is returned by the - // RTM_GETADDR request. - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // No control messages. - EXPECT_EQ(CMSG_FIRSTHDR(&msg), nullptr); -} - -// SCM_CREDENTIALS are received with SO_PASSCRED set. -TEST(NetlinkRouteTest, PasscredCreds) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - char control[CMSG_SPACE(sizeof(struct ucred))] = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - // Note: This test assumes at least one message is returned by the - // RTM_GETADDR request. - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - struct ucred creds; - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(creds))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); - - memcpy(&creds, CMSG_DATA(cmsg), sizeof(creds)); - - // The peer is the kernel, which is "PID" 0. - EXPECT_EQ(creds.pid, 0); - // The kernel identifies as root. Also allow nobody in case this test is - // running in a userns without root mapped. - EXPECT_THAT(creds.uid, AnyOf(Eq(0), Eq(65534))); - EXPECT_THAT(creds.gid, AnyOf(Eq(0), Eq(65534))); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc deleted file mode 100644 index 46f749c7c..000000000 --- a/test/syscalls/linux/socket_netlink_route_util.cc +++ /dev/null @@ -1,223 +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. - -#include "test/syscalls/linux/socket_netlink_route_util.h" - -#include <linux/if.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> - -#include "test/syscalls/linux/socket_netlink_util.h" - -namespace gvisor { -namespace testing { -namespace { - -constexpr uint32_t kSeq = 12345; - -// Types of address modifications that may be performed on an interface. -enum class LinkAddrModification { - kAdd, - kAddExclusive, - kReplace, - kDelete, -}; - -// Populates |hdr| with appripriate values for the modification type. -PosixError PopulateNlmsghdr(LinkAddrModification modification, - struct nlmsghdr* hdr) { - switch (modification) { - case LinkAddrModification::kAdd: - hdr->nlmsg_type = RTM_NEWADDR; - hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - return NoError(); - case LinkAddrModification::kAddExclusive: - hdr->nlmsg_type = RTM_NEWADDR; - hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK; - return NoError(); - case LinkAddrModification::kReplace: - hdr->nlmsg_type = RTM_NEWADDR; - hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; - return NoError(); - case LinkAddrModification::kDelete: - hdr->nlmsg_type = RTM_DELADDR; - hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - return NoError(); - } - - return PosixError(EINVAL); -} - -// Adds or removes the specified address from the specified interface. -PosixError LinkModifyLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen, - LinkAddrModification modification) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifaddrmsg ifaddr; - char attrbuf[512]; - }; - - struct request req = {}; - PosixError err = PopulateNlmsghdr(modification, &req.hdr); - if (!err.ok()) { - return err; - } - req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr)); - req.hdr.nlmsg_seq = kSeq; - req.ifaddr.ifa_index = index; - req.ifaddr.ifa_family = family; - req.ifaddr.ifa_prefixlen = prefixlen; - - struct rtattr* rta = reinterpret_cast<struct rtattr*>( - reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); - rta->rta_type = IFA_LOCAL; - rta->rta_len = RTA_LENGTH(addrlen); - req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen); - memcpy(RTA_DATA(rta), addr, addrlen); - - return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); -} - -} // namespace - -PosixError DumpLinks( - const FileDescriptor& fd, uint32_t seq, - const std::function<void(const struct nlmsghdr* hdr)>& fn) { - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = seq; - req.ifm.ifi_family = AF_UNSPEC; - - return NetlinkRequestResponse(fd, &req, sizeof(req), fn, false); -} - -PosixErrorOr<std::vector<Link>> DumpLinks() { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - std::vector<Link> links; - RETURN_IF_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) { - if (hdr->nlmsg_type != RTM_NEWLINK || - hdr->nlmsg_len < NLMSG_SPACE(sizeof(struct ifinfomsg))) { - return; - } - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - const auto* rta = FindRtAttr(hdr, msg, IFLA_IFNAME); - if (rta == nullptr) { - // Ignore links that do not have a name. - return; - } - - links.emplace_back(); - links.back().index = msg->ifi_index; - links.back().type = msg->ifi_type; - links.back().name = - std::string(reinterpret_cast<const char*>(RTA_DATA(rta))); - })); - return links; -} - -PosixErrorOr<Link> LoopbackLink() { - ASSIGN_OR_RETURN_ERRNO(auto links, DumpLinks()); - for (const auto& link : links) { - if (link.type == ARPHRD_LOOPBACK) { - return link; - } - } - return PosixError(ENOENT, "loopback link not found"); -} - -PosixError LinkAddLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen) { - return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, - LinkAddrModification::kAdd); -} - -PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen) { - return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, - LinkAddrModification::kAddExclusive); -} - -PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen) { - return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, - LinkAddrModification::kReplace); -} - -PosixError LinkDelLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen) { - return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen, - LinkAddrModification::kDelete); -} - -PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifinfo; - char pad[NLMSG_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifinfo)); - req.hdr.nlmsg_type = RTM_NEWLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - req.hdr.nlmsg_seq = kSeq; - req.ifinfo.ifi_index = index; - req.ifinfo.ifi_flags = flags; - req.ifinfo.ifi_change = change; - - return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); -} - -PosixError LinkSetMacAddr(int index, const void* addr, int addrlen) { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifinfo; - char attrbuf[512]; - }; - - struct request req = {}; - req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifinfo)); - req.hdr.nlmsg_type = RTM_NEWLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - req.hdr.nlmsg_seq = kSeq; - req.ifinfo.ifi_index = index; - - struct rtattr* rta = reinterpret_cast<struct rtattr*>( - reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); - rta->rta_type = IFLA_ADDRESS; - rta->rta_len = RTA_LENGTH(addrlen); - req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen); - memcpy(RTA_DATA(rta), addr, addrlen); - - return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h deleted file mode 100644 index eaa91ad79..000000000 --- a/test/syscalls/linux/socket_netlink_route_util.h +++ /dev/null @@ -1,68 +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. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_ - -#include <linux/netlink.h> -#include <linux/rtnetlink.h> - -#include <vector> - -#include "test/syscalls/linux/socket_netlink_util.h" - -namespace gvisor { -namespace testing { - -struct Link { - int index; - int16_t type; - std::string name; -}; - -PosixError DumpLinks(const FileDescriptor& fd, uint32_t seq, - const std::function<void(const struct nlmsghdr* hdr)>& fn); - -PosixErrorOr<std::vector<Link>> DumpLinks(); - -// Returns the loopback link on the system. ENOENT if not found. -PosixErrorOr<Link> LoopbackLink(); - -// LinkAddLocalAddr adds a new IFA_LOCAL address to the interface. -PosixError LinkAddLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen); - -// LinkAddExclusiveLocalAddr adds a new IFA_LOCAL address with NLM_F_EXCL flag -// to the interface. -PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen); - -// LinkReplaceLocalAddr replaces an IFA_LOCAL address on the interface. -PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen); - -// LinkDelLocalAddr removes IFA_LOCAL attribute on the interface. -PosixError LinkDelLocalAddr(int index, int family, int prefixlen, - const void* addr, int addrlen); - -// LinkChangeFlags changes interface flags. E.g. IFF_UP. -PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change); - -// LinkSetMacAddr sets IFLA_ADDRESS attribute of the interface. -PosixError LinkSetMacAddr(int index, const void* addr, int addrlen); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_ diff --git a/test/syscalls/linux/socket_netlink_uevent.cc b/test/syscalls/linux/socket_netlink_uevent.cc deleted file mode 100644 index da425bed4..000000000 --- a/test/syscalls/linux/socket_netlink_uevent.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <linux/filter.h> -#include <linux/netlink.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_netlink_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Tests for NETLINK_KOBJECT_UEVENT sockets. -// -// gVisor never sends any messages on these sockets, so we don't test the events -// themselves. - -namespace gvisor { -namespace testing { - -namespace { - -// SO_PASSCRED can be enabled. Since no messages are sent in gVisor, we don't -// actually test receiving credentials. -TEST(NetlinkUeventTest, PassCred) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT)); - - EXPECT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); -} - -// SO_DETACH_FILTER fails without a filter already installed. -TEST(NetlinkUeventTest, DetachNoFilter) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT)); - - int opt; - EXPECT_THAT( - setsockopt(fd.get(), SOL_SOCKET, SO_DETACH_FILTER, &opt, sizeof(opt)), - SyscallFailsWithErrno(ENOENT)); -} - -// We can attach a BPF filter. -TEST(NetlinkUeventTest, AttachFilter) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT)); - - // Minimal BPF program: a single ret. - struct sock_filter filter = {0x6, 0, 0, 0}; - struct sock_fprog prog = {}; - prog.len = 1; - prog.filter = &filter; - - EXPECT_THAT( - setsockopt(fd.get(), SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)), - SyscallSucceeds()); - - int opt; - EXPECT_THAT( - setsockopt(fd.get(), SOL_SOCKET, SO_DETACH_FILTER, &opt, sizeof(opt)), - SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_util.cc b/test/syscalls/linux/socket_netlink_util.cc deleted file mode 100644 index bdebea321..000000000 --- a/test/syscalls/linux/socket_netlink_util.cc +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_netlink_util.h" - -#include <linux/if_arp.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> -#include <sys/socket.h> - -#include <vector> - -#include "absl/strings/str_cat.h" -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<FileDescriptor> NetlinkBoundSocket(int protocol) { - FileDescriptor fd; - ASSIGN_OR_RETURN_ERRNO(fd, Socket(AF_NETLINK, SOCK_RAW, protocol)); - - struct sockaddr_nl addr = {}; - addr.nl_family = AF_NETLINK; - - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))); - MaybeSave(); - - return std::move(fd); -} - -PosixErrorOr<uint32_t> NetlinkPortID(int fd) { - struct sockaddr_nl addr; - socklen_t addrlen = sizeof(addr); - - RETURN_ERROR_IF_SYSCALL_FAIL( - getsockname(fd, reinterpret_cast<struct sockaddr*>(&addr), &addrlen)); - MaybeSave(); - - return static_cast<uint32_t>(addr.nl_pid); -} - -PosixError NetlinkRequestResponse( - const FileDescriptor& fd, void* request, size_t len, - const std::function<void(const struct nlmsghdr* hdr)>& fn, - bool expect_nlmsgerr) { - struct iovec iov = {}; - iov.iov_base = request; - iov.iov_len = len; - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(sendmsg)(fd.get(), &msg, 0)); - - return NetlinkResponse(fd, fn, expect_nlmsgerr); -} - -PosixError NetlinkResponse( - const FileDescriptor& fd, - const std::function<void(const struct nlmsghdr* hdr)>& fn, - bool expect_nlmsgerr) { - constexpr size_t kBufferSize = 4096; - std::vector<char> buf(kBufferSize); - struct iovec iov = {}; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - // If NLM_F_MULTI is set, response is a series of messages that ends with a - // NLMSG_DONE message. - int type = -1; - int flags = 0; - do { - int len; - RETURN_ERROR_IF_SYSCALL_FAIL(len = RetryEINTR(recvmsg)(fd.get(), &msg, 0)); - - // We don't bother with the complexity of dealing with truncated messages. - // We must allocate a large enough buffer up front. - if ((msg.msg_flags & MSG_TRUNC) == MSG_TRUNC) { - return PosixError(EIO, - absl::StrCat("Received truncated message with flags: ", - msg.msg_flags)); - } - - for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data()); - NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { - fn(hdr); - flags = hdr->nlmsg_flags; - type = hdr->nlmsg_type; - // Done should include an integer payload for dump_done_errno. - // See net/netlink/af_netlink.c:netlink_dump - // Some tools like the 'ip' tool check the minimum length of the - // NLMSG_DONE message. - if (type == NLMSG_DONE) { - EXPECT_GE(hdr->nlmsg_len, NLMSG_LENGTH(sizeof(int))); - } - } - } while ((flags & NLM_F_MULTI) && type != NLMSG_DONE && type != NLMSG_ERROR); - - if (expect_nlmsgerr) { - EXPECT_EQ(type, NLMSG_ERROR); - } else if (flags & NLM_F_MULTI) { - EXPECT_EQ(type, NLMSG_DONE); - } - return NoError(); -} - -PosixError NetlinkRequestResponseSingle( - const FileDescriptor& fd, void* request, size_t len, - const std::function<void(const struct nlmsghdr* hdr)>& fn) { - struct iovec iov = {}; - iov.iov_base = request; - iov.iov_len = len; - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(sendmsg)(fd.get(), &msg, 0)); - - constexpr size_t kBufferSize = 4096; - std::vector<char> buf(kBufferSize); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - int ret; - RETURN_ERROR_IF_SYSCALL_FAIL(ret = RetryEINTR(recvmsg)(fd.get(), &msg, 0)); - - // We don't bother with the complexity of dealing with truncated messages. - // We must allocate a large enough buffer up front. - if ((msg.msg_flags & MSG_TRUNC) == MSG_TRUNC) { - return PosixError( - EIO, - absl::StrCat("Received truncated message with flags: ", msg.msg_flags)); - } - - for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data()); - NLMSG_OK(hdr, ret); hdr = NLMSG_NEXT(hdr, ret)) { - fn(hdr); - } - - return NoError(); -} - -PosixError NetlinkRequestAckOrError(const FileDescriptor& fd, uint32_t seq, - void* request, size_t len) { - // Dummy negative number for no error message received. - // We won't get a negative error number so there will be no confusion. - int err = -42; - RETURN_IF_ERRNO(NetlinkRequestResponse( - fd, request, len, - [&](const struct nlmsghdr* hdr) { - EXPECT_EQ(NLMSG_ERROR, hdr->nlmsg_type); - EXPECT_EQ(hdr->nlmsg_seq, seq); - EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct nlmsgerr)); - - const struct nlmsgerr* msg = - reinterpret_cast<const struct nlmsgerr*>(NLMSG_DATA(hdr)); - err = -msg->error; - }, - true)); - return PosixError(err); -} - -const struct rtattr* FindRtAttr(const struct nlmsghdr* hdr, - const struct ifinfomsg* msg, int16_t attr) { - const int ifi_space = NLMSG_SPACE(sizeof(*msg)); - int attrlen = hdr->nlmsg_len - ifi_space; - const struct rtattr* rta = reinterpret_cast<const struct rtattr*>( - reinterpret_cast<const uint8_t*>(hdr) + NLMSG_ALIGN(ifi_space)); - for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) { - if (rta->rta_type == attr) { - return rta; - } - } - return nullptr; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_util.h b/test/syscalls/linux/socket_netlink_util.h deleted file mode 100644 index f97276d44..000000000 --- a/test/syscalls/linux/socket_netlink_util.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_ - -#include <sys/socket.h> -// socket.h has to be included before if_arp.h. -#include <linux/if_arp.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Returns a bound netlink socket. -PosixErrorOr<FileDescriptor> NetlinkBoundSocket(int protocol); - -// Returns the port ID of the passed socket. -PosixErrorOr<uint32_t> NetlinkPortID(int fd); - -// Send the passed request and call fn on all response netlink messages. -// -// To be used on requests with NLM_F_MULTI reponses. -PosixError NetlinkRequestResponse( - const FileDescriptor& fd, void* request, size_t len, - const std::function<void(const struct nlmsghdr* hdr)>& fn, - bool expect_nlmsgerr); - -// Call fn on all response netlink messages. -// -// To be used on requests with NLM_F_MULTI reponses. -PosixError NetlinkResponse( - const FileDescriptor& fd, - const std::function<void(const struct nlmsghdr* hdr)>& fn, - bool expect_nlmsgerr); - -// Send the passed request and call fn on all response netlink messages. -// -// To be used on requests without NLM_F_MULTI reponses. -PosixError NetlinkRequestResponseSingle( - const FileDescriptor& fd, void* request, size_t len, - const std::function<void(const struct nlmsghdr* hdr)>& fn); - -// Send the passed request then expect and return an ack or error. -PosixError NetlinkRequestAckOrError(const FileDescriptor& fd, uint32_t seq, - void* request, size_t len); - -// Find rtnetlink attribute in message. -const struct rtattr* FindRtAttr(const struct nlmsghdr* hdr, - const struct ifinfomsg* msg, int16_t attr); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_SOCKET_NETLINK_UTIL_H_ diff --git a/test/syscalls/linux/socket_non_blocking.cc b/test/syscalls/linux/socket_non_blocking.cc deleted file mode 100644 index c3520cadd..000000000 --- a/test/syscalls/linux/socket_non_blocking.cc +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_non_blocking.h" - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/types.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/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -TEST_P(NonBlockingSocketPairTest, ReadNothingAvailable) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[20] = {}; - ASSERT_THAT(ReadFd(sockets->first_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(NonBlockingSocketPairTest, RecvNothingAvailable) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char buf[20] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->first_fd(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(NonBlockingSocketPairTest, RecvMsgNothingAvailable) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct iovec iov; - char buf[20] = {}; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EAGAIN)); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_non_blocking.h b/test/syscalls/linux/socket_non_blocking.h deleted file mode 100644 index bd3e02fd2..000000000 --- a/test/syscalls/linux/socket_non_blocking.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected non-blocking sockets. -using NonBlockingSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_BLOCKING_H_ diff --git a/test/syscalls/linux/socket_non_stream.cc b/test/syscalls/linux/socket_non_stream.cc deleted file mode 100644 index c61817f14..000000000 --- a/test/syscalls/linux/socket_non_stream.cc +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_non_stream.h" - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(NonStreamSocketPairTest, SendMsgTooLarge) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int sndbuf; - socklen_t length = sizeof(sndbuf); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length), - SyscallSucceeds()); - - // Make the call too large to fit in the send buffer. - const int buffer_size = 3 * sndbuf; - - EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, false /* reader */), - SyscallFailsWithErrno(EMSGSIZE)); -} - -// Stream sockets allow data sent with a single (e.g. write, sendmsg) syscall -// to be read in pieces with multiple (e.g. read, recvmsg) syscalls. -// -// SplitRecv checks that control messages can only be read on the first (e.g. -// read, recvmsg) syscall, even if it doesn't provide space for the control -// message. -TEST_P(NonStreamSocketPairTest, SplitRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data) / 2]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -// Stream sockets allow data sent with multiple sends to be read in a single -// recv. Datagram sockets do not. -// -// SingleRecv checks that only a single message is readable in a single recv. -TEST_P(NonStreamSocketPairTest, SingleRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0), - SyscallSucceedsWithValue(sizeof(sent_data1))); - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0), - SyscallSucceedsWithValue(sizeof(sent_data2))); - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); -} - -TEST_P(NonStreamSocketPairTest, RecvmsgMsghdrFlagMsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) / 2] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data))); - - // Check that msghdr flags were updated. - EXPECT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); -} - -// Stream sockets allow data sent with multiple sends to be peeked at in a -// single recv. Datagram sockets (except for unix sockets) do not. -// -// SinglePeek checks that only a single message is peekable in a single recv. -TEST_P(NonStreamSocketPairTest, SinglePeek) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0), - SyscallSucceedsWithValue(sizeof(sent_data1))); - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0), - SyscallSucceedsWithValue(sizeof(sent_data2))); - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - for (int i = 0; i < 3; i++) { - memset(received_data, 0, sizeof(received_data)); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_PEEK), - SyscallSucceedsWithValue(sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - } - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(sent_data1), 0), - SyscallSucceedsWithValue(sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(sent_data2), 0), - SyscallSucceedsWithValue(sizeof(sent_data2))); - EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2))); -} - -TEST_P(NonStreamSocketPairTest, MsgTruncTruncation) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data) / 2, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); - - // Check that we didn't get any extra data. - EXPECT_NE(0, memcmp(sent_data + sizeof(sent_data) / 2, - received_data + sizeof(received_data) / 2, - sizeof(sent_data) / 2)); -} - -TEST_P(NonStreamSocketPairTest, MsgTruncTruncationRecvmsgMsghdrFlagMsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) / 2] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data))); - - // Check that msghdr flags were updated. - EXPECT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); -} - -TEST_P(NonStreamSocketPairTest, MsgTruncSameSize) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(NonStreamSocketPairTest, MsgTruncNotFull) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[2 * sizeof(sent_data)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -// This test tests reading from a socket with MSG_TRUNC and a zero length -// receive buffer. The user should be able to get the message length. -TEST_P(NonStreamSocketPairTest, RecvmsgMsgTruncZeroLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // The receive buffer is of zero length. - char received_data[0] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - // The syscall succeeds returning the full size of the message on the socket. - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // Check that MSG_TRUNC is set on msghdr flags. - EXPECT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); -} - -// This test tests reading from a socket with MSG_TRUNC | MSG_PEEK and a zero -// length receive buffer. The user should be able to get the message length -// without reading data off the socket. -TEST_P(NonStreamSocketPairTest, RecvmsgMsgTruncMsgPeekZeroLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // The receive buffer is of zero length. - char peek_data[0] = {}; - - struct iovec peek_iov; - peek_iov.iov_base = peek_data; - peek_iov.iov_len = sizeof(peek_data); - struct msghdr peek_msg = {}; - peek_msg.msg_flags = -1; - peek_msg.msg_iov = &peek_iov; - peek_msg.msg_iovlen = 1; - - // The syscall succeeds returning the full size of the message on the socket. - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &peek_msg, - MSG_TRUNC | MSG_PEEK), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // Check that MSG_TRUNC is set on msghdr flags because the receive buffer is - // smaller than the message size. - EXPECT_EQ(peek_msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - - char received_data[sizeof(sent_data)] = {}; - - struct iovec received_iov; - received_iov.iov_base = received_data; - received_iov.iov_len = sizeof(received_data); - struct msghdr received_msg = {}; - received_msg.msg_flags = -1; - received_msg.msg_iov = &received_iov; - received_msg.msg_iovlen = 1; - - // Next we can read the actual data. - ASSERT_THAT( - RetryEINTR(recvmsg)(sockets->second_fd(), &received_msg, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - // Check that MSG_TRUNC is not set on msghdr flags because we read the whole - // message. - EXPECT_EQ(received_msg.msg_flags & MSG_TRUNC, 0); -} - -// This test tests reading from a socket with MSG_TRUNC | MSG_PEEK and a zero -// length receive buffer and MSG_DONTWAIT. The user should be able to get an -// EAGAIN or EWOULDBLOCK error response. -TEST_P(NonStreamSocketPairTest, RecvmsgTruncPeekDontwaitZeroLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // NOTE: We don't send any data on the socket. - - // The receive buffer is of zero length. - char peek_data[0] = {}; - - struct iovec peek_iov; - peek_iov.iov_base = peek_data; - peek_iov.iov_len = sizeof(peek_data); - struct msghdr peek_msg = {}; - peek_msg.msg_flags = -1; - peek_msg.msg_iov = &peek_iov; - peek_msg.msg_iovlen = 1; - - // recvmsg fails with EAGAIN because no data is available on the socket. - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &peek_msg, - MSG_TRUNC | MSG_PEEK | MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_non_stream.h b/test/syscalls/linux/socket_non_stream.h deleted file mode 100644 index 469fbe6a2..000000000 --- a/test/syscalls/linux/socket_non_stream.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected non-stream sockets. -using NonStreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_H_ diff --git a/test/syscalls/linux/socket_non_stream_blocking.cc b/test/syscalls/linux/socket_non_stream_blocking.cc deleted file mode 100644 index b052f6e61..000000000 --- a/test/syscalls/linux/socket_non_stream_blocking.cc +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_non_stream_blocking.h" - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(BlockingNonStreamSocketPairTest, RecvLessThanBufferWaitAll) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) * 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_WAITALL), - SyscallSucceedsWithValue(sizeof(sent_data))); -} - -// This test tests reading from a socket with MSG_TRUNC | MSG_PEEK and a zero -// length receive buffer and MSG_DONTWAIT. The recvmsg call should block on -// reading the data. -TEST_P(BlockingNonStreamSocketPairTest, - RecvmsgTruncPeekDontwaitZeroLenBlocking) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // NOTE: We don't initially send any data on the socket. - const int data_size = 10; - char sent_data[data_size]; - RandomizeBuffer(sent_data, data_size); - - // The receive buffer is of zero length. - char peek_data[0] = {}; - - struct iovec peek_iov; - peek_iov.iov_base = peek_data; - peek_iov.iov_len = sizeof(peek_data); - struct msghdr peek_msg = {}; - peek_msg.msg_flags = -1; - peek_msg.msg_iov = &peek_iov; - peek_msg.msg_iovlen = 1; - - ScopedThread t([&]() { - // The syscall succeeds returning the full size of the message on the - // socket. This should block until there is data on the socket. - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &peek_msg, - MSG_TRUNC | MSG_PEEK), - SyscallSucceedsWithValue(data_size)); - }); - - absl::SleepFor(absl::Seconds(1)); - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), sent_data, data_size, 0), - SyscallSucceedsWithValue(data_size)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_non_stream_blocking.h b/test/syscalls/linux/socket_non_stream_blocking.h deleted file mode 100644 index 6e205a039..000000000 --- a/test/syscalls/linux/socket_non_stream_blocking.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of blocking connected non-stream -// sockets. -using BlockingNonStreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NON_STREAM_BLOCKING_H_ diff --git a/test/syscalls/linux/socket_stream.cc b/test/syscalls/linux/socket_stream.cc deleted file mode 100644 index 6522b2e01..000000000 --- a/test/syscalls/linux/socket_stream.cc +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_stream.h" - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(StreamSocketPairTest, SplitRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data) / 2]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data + sizeof(received_data), received_data, - sizeof(received_data))); -} - -// Stream sockets allow data sent with multiple sends to be read in a single -// recv. -// -// CoalescedRecv checks that multiple messages are readable in a single recv. -TEST_P(StreamSocketPairTest, CoalescedRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data1, sizeof(sent_data1), 0), - SyscallSucceedsWithValue(sizeof(sent_data1))); - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data2, sizeof(sent_data2), 0), - SyscallSucceedsWithValue(sizeof(sent_data2))); - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); -} - -TEST_P(StreamSocketPairTest, WriteOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - const char str[] = "abc"; - ASSERT_THAT(write(sockets->second_fd(), str, 3), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(StreamSocketPairTest, RecvmsgMsghdrFlagsNoMsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) / 2] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(received_data, sent_data, sizeof(received_data))); - - // Check that msghdr flags were cleared (MSG_TRUNC was not set). - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, 0); -} - -TEST_P(StreamSocketPairTest, RecvmsgTruncZeroLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[0] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_TRUNC), - SyscallSucceedsWithValue(0)); - - // Check that msghdr flags were cleared (MSG_TRUNC was not set). - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, 0); -} - -TEST_P(StreamSocketPairTest, RecvmsgTruncPeekZeroLen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[0] = {}; - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT( - RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_TRUNC | MSG_PEEK), - SyscallSucceedsWithValue(0)); - - // Check that msghdr flags were cleared (MSG_TRUNC was not set). - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, 0); -} - -TEST_P(StreamSocketPairTest, MsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)]; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data) / 2, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_stream.h b/test/syscalls/linux/socket_stream.h deleted file mode 100644 index b837b8f8c..000000000 --- a/test/syscalls/linux/socket_stream.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of blocking and non-blocking -// connected stream sockets. -using StreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_H_ diff --git a/test/syscalls/linux/socket_stream_blocking.cc b/test/syscalls/linux/socket_stream_blocking.cc deleted file mode 100644 index 538ee2268..000000000 --- a/test/syscalls/linux/socket_stream_blocking.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_stream_blocking.h" - -#include <stdio.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(BlockingStreamSocketPairTest, BlockPartialWriteClosed) { - // FIXME(b/35921550): gVisor doesn't support SO_SNDBUF on UDS, nor does it - // enforce any limit; it will write arbitrary amounts of data without - // blocking. - SKIP_IF(IsRunningOnGvisor()); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int buffer_size; - socklen_t length = sizeof(buffer_size); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, - &buffer_size, &length), - SyscallSucceeds()); - - int wfd = sockets->first_fd(); - ScopedThread t([wfd, buffer_size]() { - std::vector<char> buf(2 * buffer_size); - // Write more than fits in the buffer. Blocks then returns partial write - // when the other end is closed. The next call returns EPIPE. - // - // N.B. writes occur in chunks, so we may see less than buffer_size from - // the first call. - ASSERT_THAT(write(wfd, buf.data(), buf.size()), - SyscallSucceedsWithValue(::testing::Gt(0))); - ASSERT_THAT(write(wfd, buf.data(), buf.size()), - ::testing::AnyOf(SyscallFailsWithErrno(EPIPE), - SyscallFailsWithErrno(ECONNRESET))); - }); - - // Leave time for write to become blocked. - absl::SleepFor(absl::Seconds(1)); - - ASSERT_THAT(close(sockets->release_second_fd()), SyscallSucceeds()); -} - -// Random save may interrupt the call to sendmsg() in SendLargeSendMsg(), -// causing the write to be incomplete and the test to hang. -TEST_P(BlockingStreamSocketPairTest, SendMsgTooLarge_NoRandomSave) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int sndbuf; - socklen_t length = sizeof(sndbuf); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length), - SyscallSucceeds()); - - // Make the call too large to fit in the send buffer. - const int buffer_size = 3 * sndbuf; - - EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, true /* reader */), - SyscallSucceedsWithValue(buffer_size)); -} - -TEST_P(BlockingStreamSocketPairTest, RecvLessThanBuffer) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[200] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); -} - -// Test that MSG_WAITALL causes recv to block until all requested data is -// received. Random save can interrupt blocking and cause received data to be -// returned, even if the amount received is less than the full requested amount. -TEST_P(BlockingStreamSocketPairTest, RecvLessThanBufferWaitAll_NoRandomSave) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[100]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - constexpr auto kDuration = absl::Milliseconds(200); - auto before = Now(CLOCK_MONOTONIC); - - const ScopedThread t([&]() { - absl::SleepFor(kDuration); - - // Don't let saving after the write interrupt the blocking recv. - const DisableSave ds; - - ASSERT_THAT(write(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - }); - - char received_data[sizeof(sent_data) * 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_WAITALL), - SyscallSucceedsWithValue(sizeof(received_data))); - - auto after = Now(CLOCK_MONOTONIC); - EXPECT_GE(after - before, kDuration); -} - -TEST_P(BlockingStreamSocketPairTest, SendTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - std::vector<char> buf(kPageSize); - // We don't know how much data the socketpair will buffer, so we may do an - // arbitrarily large number of writes; saving after each write causes this - // test's time to explode. - const DisableSave ds; - for (;;) { - int ret; - ASSERT_THAT( - ret = RetryEINTR(send)(sockets->first_fd(), buf.data(), buf.size(), 0), - ::testing::AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EAGAIN))); - if (ret == -1) { - break; - } - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_stream_blocking.h b/test/syscalls/linux/socket_stream_blocking.h deleted file mode 100644 index 9fd19ff90..000000000 --- a/test/syscalls/linux/socket_stream_blocking.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of blocking connected stream -// sockets. -using BlockingStreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_BLOCKING_H_ diff --git a/test/syscalls/linux/socket_stream_nonblock.cc b/test/syscalls/linux/socket_stream_nonblock.cc deleted file mode 100644 index 74d608741..000000000 --- a/test/syscalls/linux/socket_stream_nonblock.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_stream_nonblock.h" - -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/uio.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/test_util.h" - -namespace gvisor { -namespace testing { - -using ::testing::Le; - -TEST_P(NonBlockingStreamSocketPairTest, SendMsgTooLarge) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int sndbuf; - socklen_t length = sizeof(sndbuf); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &sndbuf, &length), - SyscallSucceeds()); - - // Make the call too large to fit in the send buffer. - const int buffer_size = 3 * sndbuf; - - EXPECT_THAT(SendLargeSendMsg(sockets, buffer_size, false /* reader */), - SyscallSucceedsWithValue(Le(buffer_size))); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_stream_nonblock.h b/test/syscalls/linux/socket_stream_nonblock.h deleted file mode 100644 index c3b7fad91..000000000 --- a/test/syscalls/linux/socket_stream_nonblock.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of non-blocking connected stream -// sockets. -using NonBlockingStreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_STREAM_NONBLOCK_H_ diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc deleted file mode 100644 index b2a96086c..000000000 --- a/test/syscalls/linux/socket_test_util.cc +++ /dev/null @@ -1,957 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_test_util.h" - -#include <arpa/inet.h> -#include <poll.h> -#include <sys/socket.h> - -#include <memory> - -#include "gtest/gtest.h" -#include "absl/memory/memory.h" -#include "absl/strings/str_cat.h" -#include "absl/time/clock.h" -#include "absl/types/optional.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -Creator<SocketPair> SyscallSocketPairCreator(int domain, int type, - int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { - int pair[2]; - RETURN_ERROR_IF_SYSCALL_FAIL(socketpair(domain, type, protocol, pair)); - MaybeSave(); // Save on successful creation. - return absl::make_unique<FDSocketPair>(pair[0], pair[1]); - }; -} - -Creator<FileDescriptor> SyscallSocketCreator(int domain, int type, - int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> { - int fd = 0; - RETURN_ERROR_IF_SYSCALL_FAIL(fd = socket(domain, type, protocol)); - MaybeSave(); // Save on successful creation. - return absl::make_unique<FileDescriptor>(fd); - }; -} - -PosixErrorOr<struct sockaddr_un> UniqueUnixAddr(bool abstract, int domain) { - struct sockaddr_un addr = {}; - std::string path = NewTempAbsPathInDir("/tmp"); - if (path.size() >= sizeof(addr.sun_path)) { - return PosixError(EINVAL, - "Unable to generate a temp path of appropriate length"); - } - - if (abstract) { - // Indicate that the path is in the abstract namespace. - path[0] = 0; - } - memcpy(addr.sun_path, path.c_str(), path.length()); - addr.sun_family = domain; - return addr; -} - -Creator<SocketPair> AcceptBindSocketPairCreator(bool abstract, int domain, - int type, int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un bind_addr, - UniqueUnixAddr(abstract, domain)); - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un extra_addr, - UniqueUnixAddr(abstract, domain)); - - int bound; - RETURN_ERROR_IF_SYSCALL_FAIL(bound = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(bound, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr))); - MaybeSave(); // Successful bind. - RETURN_ERROR_IF_SYSCALL_FAIL(listen(bound, /* backlog = */ 5)); - MaybeSave(); // Successful listen. - - int connected; - RETURN_ERROR_IF_SYSCALL_FAIL(connected = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL( - connect(connected, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr))); - MaybeSave(); // Successful connect. - - int accepted; - RETURN_ERROR_IF_SYSCALL_FAIL( - accepted = accept4(bound, nullptr, nullptr, - type & (SOCK_NONBLOCK | SOCK_CLOEXEC))); - MaybeSave(); // Successful connect. - - // Cleanup no longer needed resources. - RETURN_ERROR_IF_SYSCALL_FAIL(close(bound)); - MaybeSave(); // Dropped original socket. - - // Only unlink if path is not in abstract namespace. - if (bind_addr.sun_path[0] != 0) { - RETURN_ERROR_IF_SYSCALL_FAIL(unlink(bind_addr.sun_path)); - MaybeSave(); // Unlinked path. - } - - // accepted is before connected to destruct connected before accepted. - // Destructors for nonstatic member objects are called in the reverse order - // in which they appear in the class declaration. - return absl::make_unique<AddrFDSocketPair>(accepted, connected, bind_addr, - extra_addr); - }; -} - -Creator<SocketPair> FilesystemAcceptBindSocketPairCreator(int domain, int type, - int protocol) { - return AcceptBindSocketPairCreator(/* abstract= */ false, domain, type, - protocol); -} - -Creator<SocketPair> AbstractAcceptBindSocketPairCreator(int domain, int type, - int protocol) { - return AcceptBindSocketPairCreator(/* abstract= */ true, domain, type, - protocol); -} - -Creator<SocketPair> BidirectionalBindSocketPairCreator(bool abstract, - int domain, int type, - int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1, - UniqueUnixAddr(abstract, domain)); - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2, - UniqueUnixAddr(abstract, domain)); - - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(sock1, reinterpret_cast<struct sockaddr*>(&addr1), sizeof(addr1))); - MaybeSave(); // Successful bind. - - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(sock2, reinterpret_cast<struct sockaddr*>(&addr2), sizeof(addr2))); - MaybeSave(); // Successful bind. - - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock1, reinterpret_cast<struct sockaddr*>(&addr2), sizeof(addr2))); - MaybeSave(); // Successful connect. - - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock2, reinterpret_cast<struct sockaddr*>(&addr1), sizeof(addr1))); - MaybeSave(); // Successful connect. - - // Cleanup no longer needed resources. - - // Only unlink if path is not in abstract namespace. - if (addr1.sun_path[0] != 0) { - RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr1.sun_path)); - MaybeSave(); // Successful unlink. - } - - // Only unlink if path is not in abstract namespace. - if (addr2.sun_path[0] != 0) { - RETURN_ERROR_IF_SYSCALL_FAIL(unlink(addr2.sun_path)); - MaybeSave(); // Successful unlink. - } - - return absl::make_unique<FDSocketPair>(sock1, sock2); - }; -} - -Creator<SocketPair> FilesystemBidirectionalBindSocketPairCreator(int domain, - int type, - int protocol) { - return BidirectionalBindSocketPairCreator(/* abstract= */ false, domain, type, - protocol); -} - -Creator<SocketPair> AbstractBidirectionalBindSocketPairCreator(int domain, - int type, - int protocol) { - return BidirectionalBindSocketPairCreator(/* abstract= */ true, domain, type, - protocol); -} - -Creator<SocketPair> SocketpairGoferSocketPairCreator(int domain, int type, - int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { - struct sockaddr_un addr = {}; - constexpr char kSocketGoferPath[] = "/socket"; - memcpy(addr.sun_path, kSocketGoferPath, sizeof(kSocketGoferPath)); - addr.sun_family = domain; - - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock1, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))); - MaybeSave(); // Successful connect. - - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock2, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))); - MaybeSave(); // Successful connect. - - // Make and close another socketpair to ensure that the duped ends of the - // first socketpair get closed. - // - // The problem is that there is no way to atomically send and close an FD. - // The closest that we can do is send and then immediately close the FD, - // which is what we do in the gofer. The gofer won't respond to another - // request until the reply is sent and the FD is closed, so forcing the - // gofer to handle another request will ensure that this has happened. - for (int i = 0; i < 2; i++) { - int sock; - RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol)); - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))); - RETURN_ERROR_IF_SYSCALL_FAIL(close(sock)); - } - - return absl::make_unique<FDSocketPair>(sock1, sock2); - }; -} - -Creator<SocketPair> SocketpairGoferFileSocketPairCreator(int flags) { - return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { - constexpr char kSocketGoferPath[] = "/socket"; - - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = - open(kSocketGoferPath, O_RDWR | flags)); - MaybeSave(); // Successful socket creation. - - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = - open(kSocketGoferPath, O_RDWR | flags)); - MaybeSave(); // Successful socket creation. - - return absl::make_unique<FDSocketPair>(sock1, sock2); - }; -} - -Creator<SocketPair> UnboundSocketPairCreator(bool abstract, int domain, - int type, int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr1, - UniqueUnixAddr(abstract, domain)); - ASSIGN_OR_RETURN_ERRNO(struct sockaddr_un addr2, - UniqueUnixAddr(abstract, domain)); - - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2); - }; -} - -Creator<SocketPair> FilesystemUnboundSocketPairCreator(int domain, int type, - int protocol) { - return UnboundSocketPairCreator(/* abstract= */ false, domain, type, - protocol); -} - -Creator<SocketPair> AbstractUnboundSocketPairCreator(int domain, int type, - int protocol) { - return UnboundSocketPairCreator(/* abstract= */ true, domain, type, protocol); -} - -void LocalhostAddr(struct sockaddr_in* addr, bool dual_stack) { - addr->sin_family = AF_INET; - addr->sin_port = htons(0); - inet_pton(AF_INET, "127.0.0.1", - reinterpret_cast<void*>(&addr->sin_addr.s_addr)); -} - -void LocalhostAddr(struct sockaddr_in6* addr, bool dual_stack) { - addr->sin6_family = AF_INET6; - addr->sin6_port = htons(0); - if (dual_stack) { - inet_pton(AF_INET6, "::ffff:127.0.0.1", - reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); - } else { - inet_pton(AF_INET6, "::1", - reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); - } - addr->sin6_scope_id = 0; -} - -template <typename T> -PosixErrorOr<T> BindIP(int fd, bool dual_stack) { - T addr = {}; - LocalhostAddr(&addr, dual_stack); - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))); - socklen_t addrlen = sizeof(addr); - RETURN_ERROR_IF_SYSCALL_FAIL( - getsockname(fd, reinterpret_cast<struct sockaddr*>(&addr), &addrlen)); - return addr; -} - -template <typename T> -PosixErrorOr<T> TCPBindAndListen(int fd, bool dual_stack) { - ASSIGN_OR_RETURN_ERRNO(T addr, BindIP<T>(fd, dual_stack)); - RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd, /* backlog = */ 5)); - return addr; -} - -template <typename T> -PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> -CreateTCPConnectAcceptSocketPair(int bound, int connected, int type, - bool dual_stack, T bind_addr) { - int connect_result = 0; - RETURN_ERROR_IF_SYSCALL_FAIL( - (connect_result = RetryEINTR(connect)( - connected, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr))) == -1 && - errno == EINPROGRESS - ? 0 - : connect_result); - MaybeSave(); // Successful connect. - - if (connect_result == -1) { - struct pollfd connect_poll = {connected, POLLOUT | POLLERR | POLLHUP, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(poll)(&connect_poll, 1, 0)); - int error = 0; - socklen_t errorlen = sizeof(error); - RETURN_ERROR_IF_SYSCALL_FAIL( - getsockopt(connected, SOL_SOCKET, SO_ERROR, &error, &errorlen)); - errno = error; - RETURN_ERROR_IF_SYSCALL_FAIL( - /* connect */ error == 0 ? 0 : -1); - } - - int accepted = -1; - struct pollfd accept_poll = {bound, POLLIN, 0}; - while (accepted == -1) { - RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(poll)(&accept_poll, 1, 0)); - - RETURN_ERROR_IF_SYSCALL_FAIL( - (accepted = RetryEINTR(accept4)( - bound, nullptr, nullptr, type & (SOCK_NONBLOCK | SOCK_CLOEXEC))) == - -1 && - errno == EAGAIN - ? 0 - : accepted); - } - MaybeSave(); // Successful accept. - - T extra_addr = {}; - LocalhostAddr(&extra_addr, dual_stack); - return absl::make_unique<AddrFDSocketPair>(connected, accepted, bind_addr, - extra_addr); -} - -template <typename T> -PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateTCPAcceptBindSocketPair( - int bound, int connected, int type, bool dual_stack) { - ASSIGN_OR_RETURN_ERRNO(T bind_addr, TCPBindAndListen<T>(bound, dual_stack)); - - auto result = CreateTCPConnectAcceptSocketPair(bound, connected, type, - dual_stack, bind_addr); - - // Cleanup no longer needed resources. - RETURN_ERROR_IF_SYSCALL_FAIL(close(bound)); - MaybeSave(); // Successful close. - - return result; -} - -Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type, - int protocol, - bool dual_stack) { - return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { - int bound; - RETURN_ERROR_IF_SYSCALL_FAIL(bound = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - int connected; - RETURN_ERROR_IF_SYSCALL_FAIL(connected = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - if (domain == AF_INET) { - return CreateTCPAcceptBindSocketPair<sockaddr_in>(bound, connected, type, - dual_stack); - } - return CreateTCPAcceptBindSocketPair<sockaddr_in6>(bound, connected, type, - dual_stack); - }; -} - -Creator<SocketPair> TCPAcceptBindPersistentListenerSocketPairCreator( - int domain, int type, int protocol, bool dual_stack) { - // These are lazily initialized below, on the first call to the returned - // lambda. These values are private to each returned lambda, but shared across - // invocations of a specific lambda. - // - // The sharing allows pairs created with the same parameters to share a - // listener. This prevents future connects from failing if the connecting - // socket selects a port which had previously been used by a listening socket - // that still has some connections in TIME-WAIT. - // - // The lazy initialization is to avoid creating sockets during parameter - // enumeration. This is important because parameters are enumerated during the - // build process where networking may not be available. - auto listener = std::make_shared<absl::optional<int>>(absl::optional<int>()); - auto addr4 = std::make_shared<absl::optional<sockaddr_in>>( - absl::optional<sockaddr_in>()); - auto addr6 = std::make_shared<absl::optional<sockaddr_in6>>( - absl::optional<sockaddr_in6>()); - - return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { - int connected; - RETURN_ERROR_IF_SYSCALL_FAIL(connected = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - // Share the listener across invocations. - if (!listener->has_value()) { - int fd = socket(domain, type, protocol); - if (fd < 0) { - return PosixError(errno, absl::StrCat("socket(", domain, ", ", type, - ", ", protocol, ")")); - } - listener->emplace(fd); - MaybeSave(); // Successful socket creation. - } - - // Bind the listener once, but create a new connect/accept pair each - // time. - if (domain == AF_INET) { - if (!addr4->has_value()) { - addr4->emplace( - TCPBindAndListen<sockaddr_in>(listener->value(), dual_stack) - .ValueOrDie()); - } - return CreateTCPConnectAcceptSocketPair(listener->value(), connected, - type, dual_stack, addr4->value()); - } - if (!addr6->has_value()) { - addr6->emplace( - TCPBindAndListen<sockaddr_in6>(listener->value(), dual_stack) - .ValueOrDie()); - } - return CreateTCPConnectAcceptSocketPair(listener->value(), connected, type, - dual_stack, addr6->value()); - }; -} - -template <typename T> -PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateUDPBoundSocketPair( - int sock1, int sock2, int type, bool dual_stack) { - ASSIGN_OR_RETURN_ERRNO(T addr1, BindIP<T>(sock1, dual_stack)); - ASSIGN_OR_RETURN_ERRNO(T addr2, BindIP<T>(sock2, dual_stack)); - - return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2); -} - -template <typename T> -PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> -CreateUDPBidirectionalBindSocketPair(int sock1, int sock2, int type, - bool dual_stack) { - ASSIGN_OR_RETURN_ERRNO( - auto socks, CreateUDPBoundSocketPair<T>(sock1, sock2, type, dual_stack)); - - // Connect sock1 to sock2. - RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->first_fd(), socks->second_addr(), - socks->second_addr_size())); - MaybeSave(); // Successful connection. - - // Connect sock2 to sock1. - RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->second_fd(), socks->first_addr(), - socks->first_addr_size())); - MaybeSave(); // Successful connection. - - return socks; -} - -Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, - int protocol, - bool dual_stack) { - return [=]() -> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> { - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - if (domain == AF_INET) { - return CreateUDPBidirectionalBindSocketPair<sockaddr_in>( - sock1, sock2, type, dual_stack); - } - return CreateUDPBidirectionalBindSocketPair<sockaddr_in6>(sock1, sock2, - type, dual_stack); - }; -} - -Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, - int protocol, bool dual_stack) { - return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { - int sock1; - RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - int sock2; - RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - return absl::make_unique<FDSocketPair>(sock1, sock2); - }; -} - -SocketPairKind Reversed(SocketPairKind const& base) { - auto const& creator = base.creator; - return SocketPairKind{ - absl::StrCat("reversed ", base.description), base.domain, base.type, - base.protocol, - [creator]() -> PosixErrorOr<std::unique_ptr<ReversedSocketPair>> { - ASSIGN_OR_RETURN_ERRNO(auto creator_value, creator()); - return absl::make_unique<ReversedSocketPair>(std::move(creator_value)); - }}; -} - -Creator<FileDescriptor> UnboundSocketCreator(int domain, int type, - int protocol) { - return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> { - int sock; - RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol)); - MaybeSave(); // Successful socket creation. - - return absl::make_unique<FileDescriptor>(sock); - }; -} - -std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec) { - return ApplyVecToVec<SocketPairKind>(std::vector<Middleware>{NoOp, Reversed}, - vec); -} - -SocketPairKind NoOp(SocketPairKind const& base) { return base; } - -void TransferTest(int fd1, int fd2) { - char buf1[20]; - RandomizeBuffer(buf1, sizeof(buf1)); - ASSERT_THAT(WriteFd(fd1, buf1, sizeof(buf1)), - SyscallSucceedsWithValue(sizeof(buf1))); - - char buf2[20]; - ASSERT_THAT(ReadFd(fd2, buf2, sizeof(buf2)), - SyscallSucceedsWithValue(sizeof(buf2))); - - EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); - - RandomizeBuffer(buf1, sizeof(buf1)); - ASSERT_THAT(WriteFd(fd2, buf1, sizeof(buf1)), - SyscallSucceedsWithValue(sizeof(buf1))); - - ASSERT_THAT(ReadFd(fd1, buf2, sizeof(buf2)), - SyscallSucceedsWithValue(sizeof(buf2))); - - EXPECT_EQ(0, memcmp(buf1, buf2, sizeof(buf1))); -} - -// Initializes the given buffer with random data. -void RandomizeBuffer(char* ptr, size_t len) { - uint32_t seed = time(nullptr); - for (size_t i = 0; i < len; ++i) { - ptr[i] = static_cast<char>(rand_r(&seed)); - } -} - -size_t CalculateUnixSockAddrLen(const char* sun_path) { - // Abstract addresses always return the full length. - if (sun_path[0] == 0) { - return sizeof(sockaddr_un); - } - // Filesystem addresses use the address length plus the 2 byte sun_family - // and null terminator. - return strlen(sun_path) + 3; -} - -struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_un& addr) { - struct sockaddr_storage addr_storage = {}; - memcpy(&addr_storage, &addr, sizeof(addr)); - return addr_storage; -} - -struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in& addr) { - struct sockaddr_storage addr_storage = {}; - memcpy(&addr_storage, &addr, sizeof(addr)); - return addr_storage; -} - -struct sockaddr_storage AddrFDSocketPair::to_storage(const sockaddr_in6& addr) { - struct sockaddr_storage addr_storage = {}; - memcpy(&addr_storage, &addr, sizeof(addr)); - return addr_storage; -} - -SocketKind SimpleSocket(int fam, int type, int proto) { - return SocketKind{ - absl::StrCat("Family ", fam, ", type ", type, ", proto ", proto), fam, - type, proto, SyscallSocketCreator(fam, type, proto)}; -} - -ssize_t SendLargeSendMsg(const std::unique_ptr<SocketPair>& sockets, - size_t size, bool reader) { - const int rfd = sockets->second_fd(); - ScopedThread t([rfd, size, reader] { - if (!reader) { - return; - } - - // Potentially too many syscalls in the loop. - const DisableSave ds; - - std::vector<char> buf(size); - size_t total = 0; - - while (total < size) { - int ret = read(rfd, buf.data(), buf.size()); - if (ret == -1 && errno == EAGAIN) { - continue; - } - if (ret > 0) { - total += ret; - } - - // Assert to return on first failure. - ASSERT_THAT(ret, SyscallSucceeds()); - } - }); - - std::vector<char> buf(size); - - struct iovec iov = {}; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - return RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0); -} - -namespace internal { -PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family, - SocketType type, bool reuse_addr) { - if (port < 0) { - return PosixError(EINVAL, "Invalid port"); - } - - // Both Ipv6 and Dualstack are AF_INET6. - int sock_fam = (family == AddressFamily::kIpv4 ? AF_INET : AF_INET6); - int sock_type = (type == SocketType::kTcp ? SOCK_STREAM : SOCK_DGRAM); - ASSIGN_OR_RETURN_ERRNO(auto fd, Socket(sock_fam, sock_type, 0)); - - if (reuse_addr) { - int one = 1; - RETURN_ERROR_IF_SYSCALL_FAIL( - setsockopt(fd.get(), SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))); - } - - // Try to bind. - sockaddr_storage storage = {}; - int storage_size = 0; - if (family == AddressFamily::kIpv4) { - sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&storage); - storage_size = sizeof(*addr); - addr->sin_family = AF_INET; - addr->sin_port = htons(port); - addr->sin_addr.s_addr = htonl(INADDR_ANY); - } else { - sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&storage); - storage_size = sizeof(*addr); - addr->sin6_family = AF_INET6; - addr->sin6_port = htons(port); - if (family == AddressFamily::kDualStack) { - inet_pton(AF_INET6, "::ffff:0.0.0.0", - reinterpret_cast<void*>(&addr->sin6_addr.s6_addr)); - } else { - addr->sin6_addr = in6addr_any; - } - } - - RETURN_ERROR_IF_SYSCALL_FAIL( - bind(fd.get(), reinterpret_cast<sockaddr*>(&storage), storage_size)); - - // If the user specified 0 as the port, we will return the port that the - // kernel gave us, otherwise we will validate that this socket bound to the - // requested port. - sockaddr_storage bound_storage = {}; - socklen_t bound_storage_size = sizeof(bound_storage); - RETURN_ERROR_IF_SYSCALL_FAIL( - getsockname(fd.get(), reinterpret_cast<sockaddr*>(&bound_storage), - &bound_storage_size)); - - int available_port = -1; - if (bound_storage.ss_family == AF_INET) { - sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&bound_storage); - available_port = ntohs(addr->sin_port); - } else if (bound_storage.ss_family == AF_INET6) { - sockaddr_in6* addr = reinterpret_cast<sockaddr_in6*>(&bound_storage); - available_port = ntohs(addr->sin6_port); - } else { - return PosixError(EPROTOTYPE, "Getsockname returned invalid family"); - } - - // If we requested a specific port make sure our bound port is that port. - if (port != 0 && available_port != port) { - return PosixError(EINVAL, - absl::StrCat("Bound port ", available_port, - " was not equal to requested port ", port)); - } - - // If we're trying to do a TCP socket, let's also try to listen. - if (type == SocketType::kTcp) { - RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd.get(), 1)); - } - - return available_port; -} -} // namespace internal - -PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) { - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg->msg_iov = &iov; - msg->msg_iovlen = 1; - - int ret; - RETURN_ERROR_IF_SYSCALL_FAIL(ret = RetryEINTR(sendmsg)(sock, msg, 0)); - return ret; -} - -PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout) { - fd_set rfd; - struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; - FD_ZERO(&rfd); - FD_SET(sock, &rfd); - - int ret; - RETURN_ERROR_IF_SYSCALL_FAIL(ret = select(1, &rfd, NULL, NULL, &to)); - RETURN_ERROR_IF_SYSCALL_FAIL( - ret = RetryEINTR(recv)(sock, buf, buf_size, MSG_DONTWAIT)); - return ret; -} - -PosixErrorOr<int> RecvMsgTimeout(int sock, struct msghdr* msg, int timeout) { - fd_set rfd; - struct timeval to = {.tv_sec = timeout, .tv_usec = 0}; - FD_ZERO(&rfd); - FD_SET(sock, &rfd); - - int ret; - RETURN_ERROR_IF_SYSCALL_FAIL(ret = select(1, &rfd, NULL, NULL, &to)); - RETURN_ERROR_IF_SYSCALL_FAIL( - ret = RetryEINTR(recvmsg)(sock, msg, MSG_DONTWAIT)); - return ret; -} - -void RecvNoData(int sock) { - char data = 0; - struct iovec iov; - iov.iov_base = &data; - iov.iov_len = 1; - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -TestAddress TestAddress::WithPort(uint16_t port) const { - TestAddress addr = *this; - switch (addr.family()) { - case AF_INET: - reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = htons(port); - break; - case AF_INET6: - reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = htons(port); - break; - } - return addr; -} - -TestAddress V4Any() { - TestAddress t("V4Any"); - t.addr.ss_family = AF_INET; - t.addr_len = sizeof(sockaddr_in); - reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = htonl(INADDR_ANY); - return t; -} - -TestAddress V4Loopback() { - TestAddress t("V4Loopback"); - t.addr.ss_family = AF_INET; - t.addr_len = sizeof(sockaddr_in); - reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = - htonl(INADDR_LOOPBACK); - return t; -} - -TestAddress V4MappedAny() { - TestAddress t("V4MappedAny"); - t.addr.ss_family = AF_INET6; - t.addr_len = sizeof(sockaddr_in6); - inet_pton(AF_INET6, "::ffff:0.0.0.0", - reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr); - return t; -} - -TestAddress V4MappedLoopback() { - TestAddress t("V4MappedLoopback"); - t.addr.ss_family = AF_INET6; - t.addr_len = sizeof(sockaddr_in6); - inet_pton(AF_INET6, "::ffff:127.0.0.1", - reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr); - return t; -} - -TestAddress V4Multicast() { - TestAddress t("V4Multicast"); - t.addr.ss_family = AF_INET; - t.addr_len = sizeof(sockaddr_in); - reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = - inet_addr(kMulticastAddress); - return t; -} - -TestAddress V4Broadcast() { - TestAddress t("V4Broadcast"); - t.addr.ss_family = AF_INET; - t.addr_len = sizeof(sockaddr_in); - reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = - htonl(INADDR_BROADCAST); - return t; -} - -TestAddress V6Any() { - TestAddress t("V6Any"); - t.addr.ss_family = AF_INET6; - t.addr_len = sizeof(sockaddr_in6); - reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_any; - return t; -} - -TestAddress V6Loopback() { - TestAddress t("V6Loopback"); - t.addr.ss_family = AF_INET6; - t.addr_len = sizeof(sockaddr_in6); - reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_loopback; - return t; -} - -TestAddress V6Multicast() { - TestAddress t("V6Multicast"); - t.addr.ss_family = AF_INET6; - t.addr_len = sizeof(sockaddr_in6); - EXPECT_EQ( - 1, - inet_pton(AF_INET6, "ff05::1234", - reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr)); - return t; -} - -// Checksum computes the internet checksum of a buffer. -uint16_t Checksum(uint16_t* buf, ssize_t buf_size) { - // Add up the 16-bit values in the buffer. - uint32_t total = 0; - for (unsigned int i = 0; i < buf_size; i += sizeof(*buf)) { - total += *buf; - buf++; - } - - // If buf has an odd size, add the remaining byte. - if (buf_size % 2) { - total += *(reinterpret_cast<unsigned char*>(buf) - 1); - } - - // This carries any bits past the lower 16 until everything fits in 16 bits. - while (total >> 16) { - uint16_t lower = total & 0xffff; - uint16_t upper = total >> 16; - total = lower + upper; - } - - return ~total; -} - -uint16_t IPChecksum(struct iphdr ip) { - return Checksum(reinterpret_cast<uint16_t*>(&ip), sizeof(ip)); -} - -// The pseudo-header defined in RFC 768 for calculating the UDP checksum. -struct udp_pseudo_hdr { - uint32_t srcip; - uint32_t destip; - char zero; - char protocol; - uint16_t udplen; -}; - -uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, - const char* payload, ssize_t payload_len) { - struct udp_pseudo_hdr phdr = {}; - phdr.srcip = iphdr.saddr; - phdr.destip = iphdr.daddr; - phdr.zero = 0; - phdr.protocol = IPPROTO_UDP; - phdr.udplen = udphdr.len; - - ssize_t buf_size = sizeof(phdr) + sizeof(udphdr) + payload_len; - char* buf = static_cast<char*>(malloc(buf_size)); - memcpy(buf, &phdr, sizeof(phdr)); - memcpy(buf + sizeof(phdr), &udphdr, sizeof(udphdr)); - memcpy(buf + sizeof(phdr) + sizeof(udphdr), payload, payload_len); - - uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size); - free(buf); - return csum; -} - -uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, - ssize_t payload_len) { - ssize_t buf_size = sizeof(icmphdr) + payload_len; - char* buf = static_cast<char*>(malloc(buf_size)); - memcpy(buf, &icmphdr, sizeof(icmphdr)); - memcpy(buf + sizeof(icmphdr), payload, payload_len); - - uint16_t csum = Checksum(reinterpret_cast<uint16_t*>(buf), buf_size); - free(buf); - return csum; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h deleted file mode 100644 index b3ab286b8..000000000 --- a/test/syscalls/linux/socket_test_util.h +++ /dev/null @@ -1,531 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_ - -#include <errno.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <netinet/udp.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <functional> -#include <memory> -#include <string> -#include <utility> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Wrapper for socket(2) that returns a FileDescriptor. -inline PosixErrorOr<FileDescriptor> Socket(int family, int type, int protocol) { - int fd = socket(family, type, protocol); - MaybeSave(); - if (fd < 0) { - return PosixError( - errno, absl::StrFormat("socket(%d, %d, %d)", family, type, protocol)); - } - return FileDescriptor(fd); -} - -// Wrapper for accept(2) that returns a FileDescriptor. -inline PosixErrorOr<FileDescriptor> Accept(int sockfd, sockaddr* addr, - socklen_t* addrlen) { - int fd = RetryEINTR(accept)(sockfd, addr, addrlen); - MaybeSave(); - if (fd < 0) { - return PosixError( - errno, absl::StrFormat("accept(%d, %p, %p)", sockfd, addr, addrlen)); - } - return FileDescriptor(fd); -} - -// Wrapper for accept4(2) that returns a FileDescriptor. -inline PosixErrorOr<FileDescriptor> Accept4(int sockfd, sockaddr* addr, - socklen_t* addrlen, int flags) { - int fd = RetryEINTR(accept4)(sockfd, addr, addrlen, flags); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, absl::StrFormat("accept4(%d, %p, %p, %#x)", sockfd, - addr, addrlen, flags)); - } - return FileDescriptor(fd); -} - -inline ssize_t SendFd(int fd, void* buf, size_t count, int flags) { - return internal::ApplyFileIoSyscall( - [&](size_t completed) { - return sendto(fd, static_cast<char*>(buf) + completed, - count - completed, flags, nullptr, 0); - }, - count); -} - -PosixErrorOr<struct sockaddr_un> UniqueUnixAddr(bool abstract, int domain); - -// A Creator<T> is a function that attempts to create and return a new T. (This -// is copy/pasted from cloud/gvisor/api/sandbox_util.h and is just duplicated -// here for clarity.) -template <typename T> -using Creator = std::function<PosixErrorOr<std::unique_ptr<T>>()>; - -// A SocketPair represents a pair of socket file descriptors owned by the -// SocketPair. -class SocketPair { - public: - virtual ~SocketPair() = default; - - virtual int first_fd() const = 0; - virtual int second_fd() const = 0; - virtual int release_first_fd() = 0; - virtual int release_second_fd() = 0; - virtual const struct sockaddr* first_addr() const = 0; - virtual const struct sockaddr* second_addr() const = 0; - virtual size_t first_addr_size() const = 0; - virtual size_t second_addr_size() const = 0; - virtual size_t first_addr_len() const = 0; - virtual size_t second_addr_len() const = 0; -}; - -// A FDSocketPair is a SocketPair that consists of only a pair of file -// descriptors. -class FDSocketPair : public SocketPair { - public: - FDSocketPair(int first_fd, int second_fd) - : first_(first_fd), second_(second_fd) {} - FDSocketPair(std::unique_ptr<FileDescriptor> first_fd, - std::unique_ptr<FileDescriptor> second_fd) - : first_(first_fd->release()), second_(second_fd->release()) {} - - int first_fd() const override { return first_.get(); } - int second_fd() const override { return second_.get(); } - int release_first_fd() override { return first_.release(); } - int release_second_fd() override { return second_.release(); } - const struct sockaddr* first_addr() const override { return nullptr; } - const struct sockaddr* second_addr() const override { return nullptr; } - size_t first_addr_size() const override { return 0; } - size_t second_addr_size() const override { return 0; } - size_t first_addr_len() const override { return 0; } - size_t second_addr_len() const override { return 0; } - - private: - FileDescriptor first_; - FileDescriptor second_; -}; - -// CalculateUnixSockAddrLen calculates the length returned by recvfrom(2) and -// recvmsg(2) for Unix sockets. -size_t CalculateUnixSockAddrLen(const char* sun_path); - -// A AddrFDSocketPair is a SocketPair that consists of a pair of file -// descriptors in addition to a pair of socket addresses. -class AddrFDSocketPair : public SocketPair { - public: - AddrFDSocketPair(int first_fd, int second_fd, - const struct sockaddr_un& first_address, - const struct sockaddr_un& second_address) - : first_(first_fd), - second_(second_fd), - first_addr_(to_storage(first_address)), - second_addr_(to_storage(second_address)), - first_len_(CalculateUnixSockAddrLen(first_address.sun_path)), - second_len_(CalculateUnixSockAddrLen(second_address.sun_path)), - first_size_(sizeof(first_address)), - second_size_(sizeof(second_address)) {} - - AddrFDSocketPair(int first_fd, int second_fd, - const struct sockaddr_in& first_address, - const struct sockaddr_in& second_address) - : first_(first_fd), - second_(second_fd), - first_addr_(to_storage(first_address)), - second_addr_(to_storage(second_address)), - first_len_(sizeof(first_address)), - second_len_(sizeof(second_address)), - first_size_(sizeof(first_address)), - second_size_(sizeof(second_address)) {} - - AddrFDSocketPair(int first_fd, int second_fd, - const struct sockaddr_in6& first_address, - const struct sockaddr_in6& second_address) - : first_(first_fd), - second_(second_fd), - first_addr_(to_storage(first_address)), - second_addr_(to_storage(second_address)), - first_len_(sizeof(first_address)), - second_len_(sizeof(second_address)), - first_size_(sizeof(first_address)), - second_size_(sizeof(second_address)) {} - - int first_fd() const override { return first_.get(); } - int second_fd() const override { return second_.get(); } - int release_first_fd() override { return first_.release(); } - int release_second_fd() override { return second_.release(); } - const struct sockaddr* first_addr() const override { - return reinterpret_cast<const struct sockaddr*>(&first_addr_); - } - const struct sockaddr* second_addr() const override { - return reinterpret_cast<const struct sockaddr*>(&second_addr_); - } - size_t first_addr_size() const override { return first_size_; } - size_t second_addr_size() const override { return second_size_; } - size_t first_addr_len() const override { return first_len_; } - size_t second_addr_len() const override { return second_len_; } - - private: - // to_storage coverts a sockaddr_* to a sockaddr_storage. - static struct sockaddr_storage to_storage(const sockaddr_un& addr); - static struct sockaddr_storage to_storage(const sockaddr_in& addr); - static struct sockaddr_storage to_storage(const sockaddr_in6& addr); - - FileDescriptor first_; - FileDescriptor second_; - const struct sockaddr_storage first_addr_; - const struct sockaddr_storage second_addr_; - const size_t first_len_; - const size_t second_len_; - const size_t first_size_; - const size_t second_size_; -}; - -// SyscallSocketPairCreator returns a Creator<SocketPair> that obtains file -// descriptors by invoking the socketpair() syscall. -Creator<SocketPair> SyscallSocketPairCreator(int domain, int type, - int protocol); - -// SyscallSocketCreator returns a Creator<FileDescriptor> that obtains a file -// descriptor by invoking the socket() syscall. -Creator<FileDescriptor> SyscallSocketCreator(int domain, int type, - int protocol); - -// FilesystemBidirectionalBindSocketPairCreator returns a Creator<SocketPair> -// that obtains file descriptors by invoking the bind() and connect() syscalls -// on filesystem paths. Only works for DGRAM sockets. -Creator<SocketPair> FilesystemBidirectionalBindSocketPairCreator(int domain, - int type, - int protocol); - -// AbstractBidirectionalBindSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by invoking the bind() and connect() syscalls on -// abstract namespace paths. Only works for DGRAM sockets. -Creator<SocketPair> AbstractBidirectionalBindSocketPairCreator(int domain, - int type, - int protocol); - -// SocketpairGoferSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by connect() syscalls on two sockets with socketpair -// gofer paths. -Creator<SocketPair> SocketpairGoferSocketPairCreator(int domain, int type, - int protocol); - -// SocketpairGoferFileSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by open() syscalls on socketpair gofer paths. -Creator<SocketPair> SocketpairGoferFileSocketPairCreator(int flags); - -// FilesystemAcceptBindSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by invoking the accept() and bind() syscalls on -// a filesystem path. Only works for STREAM and SEQPACKET sockets. -Creator<SocketPair> FilesystemAcceptBindSocketPairCreator(int domain, int type, - int protocol); - -// AbstractAcceptBindSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by invoking the accept() and bind() syscalls on a -// abstract namespace path. Only works for STREAM and SEQPACKET sockets. -Creator<SocketPair> AbstractAcceptBindSocketPairCreator(int domain, int type, - int protocol); - -// FilesystemUnboundSocketPairCreator returns a Creator<SocketPair> that obtains -// file descriptors by invoking the socket() syscall and generates a filesystem -// path for binding. -Creator<SocketPair> FilesystemUnboundSocketPairCreator(int domain, int type, - int protocol); - -// AbstractUnboundSocketPairCreator returns a Creator<SocketPair> that obtains -// file descriptors by invoking the socket() syscall and generates an abstract -// path for binding. -Creator<SocketPair> AbstractUnboundSocketPairCreator(int domain, int type, - int protocol); - -// TCPAcceptBindSocketPairCreator returns a Creator<SocketPair> that obtains -// file descriptors by invoking the accept() and bind() syscalls on TCP sockets. -Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type, - int protocol, - bool dual_stack); - -// TCPAcceptBindPersistentListenerSocketPairCreator is like -// TCPAcceptBindSocketPairCreator, except it uses the same listening socket to -// create all SocketPairs. -Creator<SocketPair> TCPAcceptBindPersistentListenerSocketPairCreator( - int domain, int type, int protocol, bool dual_stack); - -// UDPBidirectionalBindSocketPairCreator returns a Creator<SocketPair> that -// obtains file descriptors by invoking the bind() and connect() syscalls on UDP -// sockets. -Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, - int protocol, - bool dual_stack); - -// UDPUnboundSocketPairCreator returns a Creator<SocketPair> that obtains file -// descriptors by creating UDP sockets. -Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, - int protocol, bool dual_stack); - -// UnboundSocketCreator returns a Creator<FileDescriptor> that obtains a file -// descriptor by creating a socket. -Creator<FileDescriptor> UnboundSocketCreator(int domain, int type, - int protocol); - -// A SocketPairKind couples a human-readable description of a socket pair with -// a function that creates such a socket pair. -struct SocketPairKind { - std::string description; - int domain; - int type; - int protocol; - Creator<SocketPair> creator; - - // Create creates a socket pair of this kind. - PosixErrorOr<std::unique_ptr<SocketPair>> Create() const { return creator(); } -}; - -// A SocketKind couples a human-readable description of a socket with -// a function that creates such a socket. -struct SocketKind { - std::string description; - int domain; - int type; - int protocol; - Creator<FileDescriptor> creator; - - // Create creates a socket pair of this kind. - PosixErrorOr<std::unique_ptr<FileDescriptor>> Create() const { - return creator(); - } -}; - -// A ReversedSocketPair wraps another SocketPair but flips the first and second -// file descriptors. ReversedSocketPair is used to test socket pairs that -// should be symmetric. -class ReversedSocketPair : public SocketPair { - public: - explicit ReversedSocketPair(std::unique_ptr<SocketPair> base) - : base_(std::move(base)) {} - - int first_fd() const override { return base_->second_fd(); } - int second_fd() const override { return base_->first_fd(); } - int release_first_fd() override { return base_->release_second_fd(); } - int release_second_fd() override { return base_->release_first_fd(); } - const struct sockaddr* first_addr() const override { - return base_->second_addr(); - } - const struct sockaddr* second_addr() const override { - return base_->first_addr(); - } - size_t first_addr_size() const override { return base_->second_addr_size(); } - size_t second_addr_size() const override { return base_->first_addr_size(); } - size_t first_addr_len() const override { return base_->second_addr_len(); } - size_t second_addr_len() const override { return base_->first_addr_len(); } - - private: - std::unique_ptr<SocketPair> base_; -}; - -// Reversed returns a SocketPairKind that represents SocketPairs created by -// flipping the file descriptors provided by another SocketPair. -SocketPairKind Reversed(SocketPairKind const& base); - -// IncludeReversals returns a vector<SocketPairKind> that returns all -// SocketPairKinds in `vec` as well as all SocketPairKinds obtained by flipping -// the file descriptors provided by the kinds in `vec`. -std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec); - -// A Middleware is a function wraps a SocketPairKind. -using Middleware = std::function<SocketPairKind(SocketPairKind)>; - -// Reversed returns a SocketPairKind that represents SocketPairs created by -// flipping the file descriptors provided by another SocketPair. -template <typename T> -Middleware SetSockOpt(int level, int optname, T* value) { - return [=](SocketPairKind const& base) { - auto const& creator = base.creator; - return SocketPairKind{ - absl::StrCat("setsockopt(", level, ", ", optname, ", ", *value, ") ", - base.description), - base.domain, base.type, base.protocol, - [creator, level, optname, - value]() -> PosixErrorOr<std::unique_ptr<SocketPair>> { - ASSIGN_OR_RETURN_ERRNO(auto creator_value, creator()); - if (creator_value->first_fd() >= 0) { - RETURN_ERROR_IF_SYSCALL_FAIL(setsockopt( - creator_value->first_fd(), level, optname, value, sizeof(T))); - } - if (creator_value->second_fd() >= 0) { - RETURN_ERROR_IF_SYSCALL_FAIL(setsockopt( - creator_value->second_fd(), level, optname, value, sizeof(T))); - } - return creator_value; - }}; - }; -} - -constexpr int kSockOptOn = 1; -constexpr int kSockOptOff = 0; - -// NoOp returns the same SocketPairKind that it is passed. -SocketPairKind NoOp(SocketPairKind const& base); - -// TransferTest tests that data can be send back and fourth between two -// specified FDs. Note that calls to this function should be wrapped in -// ASSERT_NO_FATAL_FAILURE(). -void TransferTest(int fd1, int fd2); - -// Fills [buf, buf+len) with random bytes. -void RandomizeBuffer(char* buf, size_t len); - -// Base test fixture for tests that operate on pairs of connected sockets. -class SocketPairTest : public ::testing::TestWithParam<SocketPairKind> { - protected: - SocketPairTest() { - // gUnit uses printf, so so will we. - printf("Testing with %s\n", GetParam().description.c_str()); - fflush(stdout); - } - - PosixErrorOr<std::unique_ptr<SocketPair>> NewSocketPair() const { - return GetParam().Create(); - } -}; - -// Base test fixture for tests that operate on simple Sockets. -class SimpleSocketTest : public ::testing::TestWithParam<SocketKind> { - protected: - SimpleSocketTest() { - // gUnit uses printf, so so will we. - printf("Testing with %s\n", GetParam().description.c_str()); - } - - PosixErrorOr<std::unique_ptr<FileDescriptor>> NewSocket() const { - return GetParam().Create(); - } -}; - -SocketKind SimpleSocket(int fam, int type, int proto); - -// Send a buffer of size 'size' to sockets->first_fd(), returning the result of -// sendmsg. -// -// If reader, read from second_fd() until size bytes have been read. -ssize_t SendLargeSendMsg(const std::unique_ptr<SocketPair>& sockets, - size_t size, bool reader); - -// Initializes the given buffer with random data. -void RandomizeBuffer(char* ptr, size_t len); - -enum class AddressFamily { kIpv4 = 1, kIpv6 = 2, kDualStack = 3 }; -enum class SocketType { kUdp = 1, kTcp = 2 }; - -// Returns a PosixError or a port that is available. If 0 is specified as the -// port it will bind port 0 (and allow the kernel to select any free port). -// Otherwise, it will try to bind the specified port and validate that it can be -// used for the requested family and socket type. The final option is -// reuse_addr. This specifies whether SO_REUSEADDR should be applied before a -// bind(2) attempt. SO_REUSEADDR means that sockets in TIME_WAIT states or other -// bound UDP sockets would not cause an error on bind(2). This option should be -// set if subsequent calls to bind on the returned port will also use -// SO_REUSEADDR. -// -// Note: That this test will attempt to bind the ANY address for the respective -// protocol. -PosixErrorOr<int> PortAvailable(int port, AddressFamily family, SocketType type, - bool reuse_addr); - -// FreeAvailablePort is used to return a port that was obtained by using -// the PortAvailable helper with port 0. -PosixError FreeAvailablePort(int port); - -// SendMsg converts a buffer to an iovec and adds it to msg before sending it. -PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size); - -// RecvTimeout calls select on sock with timeout and then calls recv on sock. -PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout); - -// RecvMsgTimeout calls select on sock with timeout and then calls recvmsg on -// sock. -PosixErrorOr<int> RecvMsgTimeout(int sock, msghdr* msg, int timeout); - -// RecvNoData checks that no data is receivable on sock. -void RecvNoData(int sock); - -// Base test fixture for tests that apply to all kinds of pairs of connected -// sockets. -using AllSocketPairTest = SocketPairTest; - -struct TestAddress { - std::string description; - sockaddr_storage addr; - socklen_t addr_len; - - explicit TestAddress(std::string description = "") - : description(std::move(description)), addr(), addr_len() {} - - int family() const { return addr.ss_family; } - - // Returns a new TestAddress with specified port. If port is not supported, - // the same TestAddress is returned. - TestAddress WithPort(uint16_t port) const; -}; - -constexpr char kMulticastAddress[] = "224.0.2.1"; -constexpr char kBroadcastAddress[] = "255.255.255.255"; - -TestAddress V4Any(); -TestAddress V4Broadcast(); -TestAddress V4Loopback(); -TestAddress V4MappedAny(); -TestAddress V4MappedLoopback(); -TestAddress V4Multicast(); -TestAddress V6Any(); -TestAddress V6Loopback(); -TestAddress V6Multicast(); - -// Compute the internet checksum of an IP header. -uint16_t IPChecksum(struct iphdr ip); - -// Compute the internet checksum of a UDP header. -uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, - const char* payload, ssize_t payload_len); - -// Compute the internet checksum of an ICMP header. -uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, - ssize_t payload_len); - -namespace internal { -PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family, - SocketType type, bool reuse_addr); -} // namespace internal - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_SOCKET_TEST_UTIL_H_ diff --git a/test/syscalls/linux/socket_test_util_impl.cc b/test/syscalls/linux/socket_test_util_impl.cc deleted file mode 100644 index ef661a0e3..000000000 --- a/test/syscalls/linux/socket_test_util_impl.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<int> PortAvailable(int port, AddressFamily family, SocketType type, - bool reuse_addr) { - return internal::TryPortAvailable(port, family, type, reuse_addr); -} - -PosixError FreeAvailablePort(int port) { return NoError(); } - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix.cc b/test/syscalls/linux/socket_unix.cc deleted file mode 100644 index 591cab3fd..000000000 --- a/test/syscalls/linux/socket_unix.cc +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_unix.h" - -#include <errno.h> -#include <net/if.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -// This file contains tests specific to Unix domain sockets. It does not contain -// tests for UDS control messages. Those belong in socket_unix_cmsg.cc. -// -// This file is a generic socket test file. It must be built with another file -// that provides the test types. - -namespace gvisor { -namespace testing { - -namespace { - -TEST_P(UnixSocketPairTest, InvalidGetSockOpt) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int opt; - socklen_t optlen = sizeof(opt); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, -1, &opt, &optlen), - SyscallFailsWithErrno(ENOPROTOOPT)); -} - -TEST_P(UnixSocketPairTest, BindToBadName) { - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - constexpr char kBadName[] = "/some/path/that/does/not/exist"; - sockaddr_un sockaddr; - sockaddr.sun_family = AF_LOCAL; - memcpy(sockaddr.sun_path, kBadName, sizeof(kBadName)); - - EXPECT_THAT( - bind(pair->first_fd(), reinterpret_cast<struct sockaddr*>(&sockaddr), - sizeof(sockaddr)), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(UnixSocketPairTest, BindToBadFamily) { - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - constexpr char kBadName[] = "/some/path/that/does/not/exist"; - sockaddr_un sockaddr; - sockaddr.sun_family = AF_INET; - memcpy(sockaddr.sun_path, kBadName, sizeof(kBadName)); - - EXPECT_THAT( - bind(pair->first_fd(), reinterpret_cast<struct sockaddr*>(&sockaddr), - sizeof(sockaddr)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UnixSocketPairTest, RecvmmsgTimeoutAfterRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[10]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - char received_data[sizeof(sent_data) * 2]; - std::vector<struct mmsghdr> msgs(2); - std::vector<struct iovec> iovs(msgs.size()); - const int chunk_size = sizeof(received_data) / msgs.size(); - for (size_t i = 0; i < msgs.size(); i++) { - iovs[i].iov_len = chunk_size; - iovs[i].iov_base = &received_data[i * chunk_size]; - msgs[i].msg_hdr.msg_iov = &iovs[i]; - msgs[i].msg_hdr.msg_iovlen = 1; - } - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - struct timespec timeout = {0, 1}; - ASSERT_THAT(RetryEINTR(recvmmsg)(sockets->second_fd(), &msgs[0], msgs.size(), - 0, &timeout), - SyscallSucceedsWithValue(1)); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - EXPECT_EQ(chunk_size, msgs[0].msg_len); -} - -TEST_P(UnixSocketPairTest, TIOCINQSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - if (IsRunningOnGvisor()) { - // TODO(gvisor.dev/issue/273): Inherited host UDS don't support TIOCINQ. - // Skip the test. - int size = -1; - int ret = ioctl(sockets->first_fd(), TIOCINQ, &size); - SKIP_IF(ret == -1 && errno == ENOTTY); - } - - int size = -1; - EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, 0); - - const char some_data[] = "dangerzone"; - ASSERT_THAT( - RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0), - SyscallSucceeds()); - EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, sizeof(some_data)); - - // Linux only reports the first message's size, which is wrong. We test for - // the behavior described in the man page. - SKIP_IF(!IsRunningOnGvisor()); - - ASSERT_THAT( - RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0), - SyscallSucceeds()); - EXPECT_THAT(ioctl(sockets->first_fd(), TIOCINQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, sizeof(some_data) * 2); -} - -TEST_P(UnixSocketPairTest, TIOCOUTQSucceeds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - if (IsRunningOnGvisor()) { - // TODO(gvisor.dev/issue/273): Inherited host UDS don't support TIOCOUTQ. - // Skip the test. - int size = -1; - int ret = ioctl(sockets->second_fd(), TIOCOUTQ, &size); - SKIP_IF(ret == -1 && errno == ENOTTY); - } - - int size = -1; - EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, 0); - - // Linux reports bogus numbers which are related to its internal allocations. - // We test for the behavior described in the man page. - SKIP_IF(!IsRunningOnGvisor()); - - const char some_data[] = "dangerzone"; - ASSERT_THAT( - RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0), - SyscallSucceeds()); - EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, sizeof(some_data)); - - ASSERT_THAT( - RetryEINTR(send)(sockets->second_fd(), &some_data, sizeof(some_data), 0), - SyscallSucceeds()); - EXPECT_THAT(ioctl(sockets->second_fd(), TIOCOUTQ, &size), SyscallSucceeds()); - EXPECT_EQ(size, sizeof(some_data) * 2); -} - -TEST_P(UnixSocketPairTest, NetdeviceIoctlsSucceed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Prepare the request. - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - - // Check that the ioctl either succeeds or fails with ENODEV. - int err = ioctl(sockets->first_fd(), SIOCGIFINDEX, &ifr); - if (err < 0) { - ASSERT_EQ(errno, ENODEV); - } -} - -TEST_P(UnixSocketPairTest, Shutdown) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - const std::string data = "abc"; - ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds()); - ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RDWR), SyscallSucceeds()); - - // Shutting down a socket does not clear the buffer. - char buf[3]; - ASSERT_THAT(ReadFd(sockets->second_fd(), buf, data.size()), - SyscallSucceedsWithValue(data.size())); - EXPECT_EQ(data, absl::string_view(buf, data.size())); -} - -TEST_P(UnixSocketPairTest, ShutdownRead) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RD), SyscallSucceeds()); - - // When the socket is shutdown for read, read behavior varies between - // different socket types. This is covered by the various ReadOneSideClosed - // test cases. - - // ... and the peer cannot write. - const std::string data = "abc"; - EXPECT_THAT(WriteFd(sockets->second_fd(), data.c_str(), data.size()), - SyscallFailsWithErrno(EPIPE)); - - // ... but the socket can still write. - ASSERT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - - // ... and the peer can still read. - char buf[3]; - EXPECT_THAT(ReadFd(sockets->second_fd(), buf, data.size()), - SyscallSucceedsWithValue(data.size())); - EXPECT_EQ(data, absl::string_view(buf, data.size())); -} - -TEST_P(UnixSocketPairTest, ShutdownWrite) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_WR), SyscallSucceeds()); - - // When the socket is shutdown for write, it cannot write. - const std::string data = "abc"; - EXPECT_THAT(WriteFd(sockets->first_fd(), data.c_str(), data.size()), - SyscallFailsWithErrno(EPIPE)); - - // ... and the peer read behavior varies between different socket types. This - // is covered by the various ReadOneSideClosed test cases. - - // ... but the peer can still write. - char buf[3]; - ASSERT_THAT(WriteFd(sockets->second_fd(), data.c_str(), data.size()), - SyscallSucceedsWithValue(data.size())); - - // ... and the socket can still read. - EXPECT_THAT(ReadFd(sockets->first_fd(), buf, data.size()), - SyscallSucceedsWithValue(data.size())); - EXPECT_EQ(data, absl::string_view(buf, data.size())); -} - -TEST_P(UnixSocketPairTest, SocketReopenFromProcfs) { - // TODO(gvisor.dev/issue/1624): In VFS1, we return EIO instead of ENXIO (see - // b/122310852). Remove this skip once VFS1 is deleted. - SKIP_IF(IsRunningWithVFS1()); - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Opening a socket pair via /proc/self/fd/X is a ENXIO. - for (const int fd : {sockets->first_fd(), sockets->second_fd()}) { - ASSERT_THAT(Open(absl::StrCat("/proc/self/fd/", fd), O_WRONLY), - PosixErrorIs(ENXIO, ::testing::_)); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix.h b/test/syscalls/linux/socket_unix.h deleted file mode 100644 index 3625cc404..000000000 --- a/test/syscalls/linux/socket_unix.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected unix sockets. -using UnixSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_H_ diff --git a/test/syscalls/linux/socket_unix_abstract_nonblock.cc b/test/syscalls/linux/socket_unix_abstract_nonblock.cc deleted file mode 100644 index 8bef76b67..000000000 --- a/test/syscalls/linux/socket_unix_abstract_nonblock.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingAbstractUnixSockets, NonBlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_blocking_local.cc b/test/syscalls/linux/socket_unix_blocking_local.cc deleted file mode 100644 index 77cb8c6d6..000000000 --- a/test/syscalls/linux/socket_unix_blocking_local.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM}), - ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM}), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - std::vector<int>{SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM})); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingUnixDomainSockets, BlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_cmsg.cc b/test/syscalls/linux/socket_unix_cmsg.cc deleted file mode 100644 index 22a4ee0d1..000000000 --- a/test/syscalls/linux/socket_unix_cmsg.cc +++ /dev/null @@ -1,1501 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_unix_cmsg.h" - -#include <errno.h> -#include <net/if.h> -#include <stdio.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/un.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -// This file contains tests for control message in Unix domain sockets. -// -// This file is a generic socket test file. It must be built with another file -// that provides the test types. - -namespace gvisor { -namespace testing { - -namespace { - -TEST_P(UnixSocketPairCmsgTest, BasicFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[20]; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, BasicTwoFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - int sent_fds[] = {pair1->second_fd(), pair2->second_fd()}; - - ASSERT_NO_FATAL_FAILURE( - SendFDs(sockets->first_fd(), sent_fds, 2, sent_data, sizeof(sent_data))); - - char received_data[20]; - int received_fds[] = {-1, -1}; - - ASSERT_NO_FATAL_FAILURE(RecvFDs(sockets->second_fd(), received_fds, 2, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[0], pair1->first_fd())); - ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[1], pair2->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, BasicThreeFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - auto pair3 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - int sent_fds[] = {pair1->second_fd(), pair2->second_fd(), pair3->second_fd()}; - - ASSERT_NO_FATAL_FAILURE( - SendFDs(sockets->first_fd(), sent_fds, 3, sent_data, sizeof(sent_data))); - - char received_data[20]; - int received_fds[] = {-1, -1, -1}; - - ASSERT_NO_FATAL_FAILURE(RecvFDs(sockets->second_fd(), received_fds, 3, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[0], pair1->first_fd())); - ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[1], pair2->first_fd())); - ASSERT_NO_FATAL_FAILURE(TransferTest(received_fds[2], pair3->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, BadFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - int sent_fd = -1; - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(sent_fd))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(sent_fd)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmsg), &sent_fd, sizeof(sent_fd)); - - struct iovec iov; - iov.iov_base = sent_data; - iov.iov_len = sizeof(sent_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EBADF)); -} - -TEST_P(UnixSocketPairCmsgTest, ShortCmsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - int sent_fd = -1; - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(sent_fd))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = 1; - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmsg), &sent_fd, sizeof(sent_fd)); - - struct iovec iov; - iov.iov_base = sent_data; - iov.iov_len = sizeof(sent_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// BasicFDPassNoSpace starts off by sending a single FD just like BasicFDPass. -// The difference is that when calling recvmsg, no space for FDs is provided, -// only space for the cmsg header. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassNoSpace) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[20]; - - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(0)); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_controllen, 0); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -// BasicFDPassNoSpaceMsgCtrunc sends an FD, but does not provide any space to -// receive it. It then verifies that the MSG_CTRUNC flag is set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassNoSpaceMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(0)); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - char received_data[sizeof(sent_data)]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_controllen, 0); - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); -} - -// BasicFDPassNullControlMsgCtrunc sends an FD and sets contradictory values for -// msg_controllen and msg_control. msg_controllen is set to the correct size to -// accommodate the FD, but msg_control is set to NULL. In this case, msg_control -// should override msg_controllen. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassNullControlMsgCtrunc) { - // FIXME(gvisor.dev/issue/207): Fix handling of NULL msg_control. - SKIP_IF(IsRunningOnGvisor()); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - msg.msg_controllen = CMSG_SPACE(1); - - char received_data[sizeof(sent_data)]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_controllen, 0); - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); -} - -// BasicFDPassNotEnoughSpaceMsgCtrunc sends an FD, but does not provide enough -// space to receive it. It then verifies that the MSG_CTRUNC flag is set in the -// msghdr. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassNotEnoughSpaceMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(0) + 1); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - char received_data[sizeof(sent_data)]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_controllen, 0); - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); -} - -// BasicThreeFDPassTruncationMsgCtrunc sends three FDs, but only provides enough -// space to receive two of them. It then verifies that the MSG_CTRUNC flag is -// set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, BasicThreeFDPassTruncationMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - auto pair3 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - int sent_fds[] = {pair1->second_fd(), pair2->second_fd(), pair3->second_fd()}; - - ASSERT_NO_FATAL_FAILURE( - SendFDs(sockets->first_fd(), sent_fds, 3, sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(2 * sizeof(int))); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - char received_data[sizeof(sent_data)]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(2 * sizeof(int))); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS); -} - -// BasicFDPassUnalignedRecv starts off by sending a single FD just like -// BasicFDPass. The difference is that when calling recvmsg, the length of the -// receive data is only aligned on a 4 byte boundary instead of the normal 8. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassUnalignedRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[20]; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFDUnaligned( - sockets->second_fd(), &fd, received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -// BasicFDPassUnalignedRecvNoMsgTrunc sends one FD and only provides enough -// space to receive just it. (Normally the minimum amount of space one would -// provide would be enough space for two FDs.) It then verifies that the -// MSG_CTRUNC flag is not set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, BasicFDPassUnalignedRecvNoMsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int)) - sizeof(int)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_flags, 0); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS); -} - -// BasicTwoFDPassUnalignedRecvTruncationMsgTrunc sends two FDs, but only -// provides enough space to receive one of them. It then verifies that the -// MSG_CTRUNC flag is set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, BasicTwoFDPassUnalignedRecvTruncationMsgTrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - int sent_fds[] = {pair->first_fd(), pair->second_fd()}; - - ASSERT_NO_FATAL_FAILURE( - SendFDs(sockets->first_fd(), sent_fds, 2, sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - // CMSG_SPACE rounds up to two FDs, we only want one. - char control[CMSG_SPACE(sizeof(int)) - sizeof(int)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_RIGHTS); -} - -TEST_P(UnixSocketPairCmsgTest, ConcurrentBasicFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - int sockfd1 = sockets->first_fd(); - auto recv_func = [sockfd1, sent_data]() { - char received_data[20]; - int fd = -1; - RecvSingleFD(sockfd1, &fd, received_data, sizeof(received_data)); - ASSERT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - char buf[20]; - ASSERT_THAT(ReadFd(fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(WriteFd(fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - }; - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->second_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - ScopedThread t(recv_func); - - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(WriteFd(pair->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[20]; - ASSERT_THAT(ReadFd(pair->first_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - - t.Join(); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -// FDPassNoRecv checks that the control message can be safely ignored by using -// read(2) instead of recvmsg(2). -TEST_P(UnixSocketPairCmsgTest, FDPassNoRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - // Read while ignoring the passed FD. - char received_data[20]; - ASSERT_THAT( - ReadFd(sockets->second_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - // Check that the socket still works for reads and writes. - ASSERT_NO_FATAL_FAILURE( - TransferTest(sockets->first_fd(), sockets->second_fd())); -} - -// FDPassInterspersed1 checks that sent control messages cannot be read before -// their associated data has been read. -TEST_P(UnixSocketPairCmsgTest, FDPassInterspersed1) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char written_data[20]; - RandomizeBuffer(written_data, sizeof(written_data)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), written_data, sizeof(written_data)), - SyscallSucceedsWithValue(sizeof(written_data))); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - // Check that we don't get a control message, but do get the data. - char received_data[20]; - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)); - EXPECT_EQ(0, memcmp(written_data, received_data, sizeof(written_data))); -} - -// FDPassInterspersed2 checks that sent control messages cannot be read after -// their associated data has been read while ignoring the control message by -// using read(2) instead of recvmsg(2). -TEST_P(UnixSocketPairCmsgTest, FDPassInterspersed2) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char written_data[20]; - RandomizeBuffer(written_data, sizeof(written_data)); - ASSERT_THAT(WriteFd(sockets->first_fd(), written_data, sizeof(written_data)), - SyscallSucceedsWithValue(sizeof(written_data))); - - char received_data[20]; - ASSERT_THAT( - ReadFd(sockets->second_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - EXPECT_EQ(0, memcmp(written_data, received_data, sizeof(written_data))); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassNotCoalesced) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(), - sent_data1, sizeof(sent_data1))); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(), - sent_data2, sizeof(sent_data2))); - - char received_data1[sizeof(sent_data1) + sizeof(sent_data2)]; - int received_fd1 = -1; - - RecvSingleFD(sockets->second_fd(), &received_fd1, received_data1, - sizeof(received_data1), sizeof(sent_data1)); - - EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1))); - TransferTest(pair1->first_fd(), pair1->second_fd()); - - char received_data2[sizeof(sent_data1) + sizeof(sent_data2)]; - int received_fd2 = -1; - - RecvSingleFD(sockets->second_fd(), &received_fd2, received_data2, - sizeof(received_data2), sizeof(sent_data2)); - - EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2))); - TransferTest(pair2->first_fd(), pair2->second_fd()); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassPeek) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char peek_data[20]; - int peek_fd = -1; - PeekSingleFD(sockets->second_fd(), &peek_fd, peek_data, sizeof(peek_data)); - EXPECT_EQ(0, memcmp(sent_data, peek_data, sizeof(sent_data))); - TransferTest(peek_fd, pair->first_fd()); - EXPECT_THAT(close(peek_fd), SyscallSucceeds()); - - char received_data[20]; - int received_fd = -1; - RecvSingleFD(sockets->second_fd(), &received_fd, received_data, - sizeof(received_data)); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - TransferTest(received_fd, pair->first_fd()); - EXPECT_THAT(close(received_fd), SyscallSucceeds()); -} - -TEST_P(UnixSocketPairCmsgTest, BasicCredPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - EXPECT_EQ(sent_creds.pid, received_creds.pid); - EXPECT_EQ(sent_creds.uid, received_creds.uid); - EXPECT_EQ(sent_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredRecvEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, SendNullCredsAfterSoPassCredRecvEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - SetSoPassCred(sockets->second_fd()); - - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data))); - - char received_data[20]; - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, SendNullCredsBeforeSoPassCredSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->first_fd()); - - char received_data[20]; - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(UnixSocketPairCmsgTest, SendNullCredsAfterSoPassCredSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - SetSoPassCred(sockets->first_fd()); - - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data))); - - char received_data[20]; - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(UnixSocketPairCmsgTest, - SendNullCredsBeforeSoPassCredRecvEndAfterSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - SetSoPassCred(sockets->first_fd()); - - ASSERT_NO_FATAL_FAILURE( - SendNullCmsg(sockets->first_fd(), sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredRecvEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, WriteAfterSoPassCredRecvEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[20]; - - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - SetSoPassCred(sockets->first_fd()); - - char received_data[20]; - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(UnixSocketPairCmsgTest, WriteAfterSoPassCredSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->first_fd()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[20]; - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(UnixSocketPairCmsgTest, WriteBeforeSoPassCredRecvEndAfterSendEnd) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - SetSoPassCred(sockets->first_fd()); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data)), - SyscallSucceedsWithValue(sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixSocketPairCmsgTest, CredPassTruncated) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - struct msghdr msg = {}; - char control[CMSG_SPACE(0) + sizeof(pid_t)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - EXPECT_EQ(msg.msg_controllen, sizeof(control)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, sizeof(control)); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); - - pid_t pid = 0; - memcpy(&pid, CMSG_DATA(cmsg), sizeof(pid)); - EXPECT_EQ(pid, sent_creds.pid); -} - -// CredPassNoMsgCtrunc passes a full set of credentials. It then verifies that -// receiving the full set does not result in MSG_CTRUNC being set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, CredPassNoMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(struct ucred))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - // The control message should not be truncated. - EXPECT_EQ(msg.msg_flags, 0); - EXPECT_EQ(msg.msg_controllen, sizeof(control)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct ucred))); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); -} - -// CredPassNoSpaceMsgCtrunc passes a full set of credentials. It then receives -// the data without providing space for any credentials and verifies that -// MSG_CTRUNC is set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, CredPassNoSpaceMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - struct msghdr msg = {}; - char control[CMSG_SPACE(0)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - // The control message should be truncated. - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); - EXPECT_EQ(msg.msg_controllen, sizeof(control)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, sizeof(control)); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); -} - -// CredPassTruncatedMsgCtrunc passes a full set of credentials. It then receives -// the data while providing enough space for only the first field of the -// credentials and verifies that MSG_CTRUNC is set in the msghdr. -TEST_P(UnixSocketPairCmsgTest, CredPassTruncatedMsgCtrunc) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - struct msghdr msg = {}; - char control[CMSG_SPACE(0) + sizeof(pid_t)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[sizeof(sent_data)] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - // The control message should be truncated. - EXPECT_EQ(msg.msg_flags, MSG_CTRUNC); - EXPECT_EQ(msg.msg_controllen, sizeof(control)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, sizeof(control)); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); -} - -TEST_P(UnixSocketPairCmsgTest, SoPassCred) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int opt; - socklen_t optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_FALSE(opt); - - optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_FALSE(opt); - - SetSoPassCred(sockets->first_fd()); - - optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_TRUE(opt); - - optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_FALSE(opt); - - int zero = 0; - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &zero, - sizeof(zero)), - SyscallSucceeds()); - - optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_FALSE(opt); - - optLen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &opt, &optLen), - SyscallSucceeds()); - EXPECT_FALSE(opt); -} - -TEST_P(UnixSocketPairCmsgTest, NoDataCredPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct msghdr msg = {}; - - struct iovec iov; - iov.iov_base = sent_data; - iov.iov_len = sizeof(sent_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - char control[CMSG_SPACE(0)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(0); - - ASSERT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UnixSocketPairCmsgTest, NoPassCred) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - char received_data[20]; - - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(UnixSocketPairCmsgTest, CredAndFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendCredsAndFD(sockets->first_fd(), sent_creds, - pair->second_fd(), sent_data, - sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - struct ucred received_creds; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds, - &fd, received_data, - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - EXPECT_EQ(sent_creds.pid, received_creds.pid); - EXPECT_EQ(sent_creds.uid, received_creds.uid); - EXPECT_EQ(sent_creds.gid, received_creds.gid); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassBeforeSoPassCred) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[20]; - struct ucred received_creds; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds, - &fd, received_data, - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCred) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - SetSoPassCred(sockets->second_fd()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[20]; - struct ucred received_creds; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvCredsAndFD(sockets->second_fd(), &received_creds, - &fd, received_data, - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -TEST_P(UnixSocketPairCmsgTest, CloexecDroppedWhenFDPassed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = ASSERT_NO_ERRNO_AND_VALUE( - UnixDomainSocketPair(SOCK_SEQPACKET | SOCK_CLOEXEC).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[20]; - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data))); - - EXPECT_THAT(fcntl(fd, F_GETFD), SyscallSucceedsWithValue(0)); -} - -TEST_P(UnixSocketPairCmsgTest, CloexecRecvFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - char received_data[20]; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CMSG_CLOEXEC), - SyscallSucceedsWithValue(sizeof(received_data))); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS); - - int fd = -1; - memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); - - EXPECT_THAT(fcntl(fd, F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCredWithoutCredSpace) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - SetSoPassCred(sockets->second_fd()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - char control[CMSG_LEN(0)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[20]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - EXPECT_EQ(msg.msg_controllen, sizeof(control)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, sizeof(control)); - EXPECT_EQ(cmsg->cmsg_level, SOL_SOCKET); - EXPECT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); -} - -// This test will validate that MSG_CTRUNC as an input flag to recvmsg will -// not appear as an output flag on the control message when truncation doesn't -// happen. -TEST_P(UnixSocketPairCmsgTest, MsgCtruncInputIsNoop) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int)) /* we're passing a single fd */]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - char received_data[20]; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, MSG_CTRUNC), - SyscallSucceedsWithValue(sizeof(received_data))); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS); - - // Now we should verify that MSG_CTRUNC wasn't set as an output flag. - EXPECT_EQ(msg.msg_flags & MSG_CTRUNC, 0); -} - -TEST_P(UnixSocketPairCmsgTest, FDPassAfterSoPassCredWithoutCredHeaderSpace) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - SetSoPassCred(sockets->second_fd()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - struct msghdr msg = {}; - char control[CMSG_LEN(0) / 2]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - char received_data[20]; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sockets->second_fd(), &msg, 0), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - EXPECT_EQ(msg.msg_controllen, 0); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_cmsg.h b/test/syscalls/linux/socket_unix_cmsg.h deleted file mode 100644 index 431606903..000000000 --- a/test/syscalls/linux/socket_unix_cmsg.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected unix sockets about -// control messages. -using UnixSocketPairCmsgTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_ diff --git a/test/syscalls/linux/socket_unix_dgram.cc b/test/syscalls/linux/socket_unix_dgram.cc deleted file mode 100644 index 5b0844493..000000000 --- a/test/syscalls/linux/socket_unix_dgram.cc +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_unix_dgram.h" - -#include <stdio.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST_P(DgramUnixSocketPairTest, WriteOneSideClosed) { - // FIXME(b/35925052): gVisor datagram sockets return EPIPE instead of - // ECONNREFUSED. - SKIP_IF(IsRunningOnGvisor()); - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - constexpr char kStr[] = "abc"; - ASSERT_THAT(write(sockets->second_fd(), kStr, 3), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -TEST_P(DgramUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int sock = sockets->first_fd(); - int buf_size = 0; - socklen_t buf_size_len = sizeof(buf_size); - ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), - SyscallSucceeds()); - int opts; - ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); - - std::vector<char> buf(buf_size / 4); - // Write till the socket buffer is full. - while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { - // Sleep to give linux a chance to move data from the send buffer to the - // receive buffer. - absl::SleepFor(absl::Milliseconds(10)); // 10ms. - } - // The last error should have been EWOULDBLOCK. - ASSERT_EQ(errno, EWOULDBLOCK); - - // Now increase the socket send buffer. - buf_size = buf_size * 2; - ASSERT_THAT( - setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), - SyscallSucceeds()); - - // The send should succeed again. - ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), - SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_dgram.h b/test/syscalls/linux/socket_unix_dgram.h deleted file mode 100644 index 0764ef85b..000000000 --- a/test/syscalls/linux/socket_unix_dgram.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected dgram unix sockets. -using DgramUnixSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_DGRAM_H_ diff --git a/test/syscalls/linux/socket_unix_dgram_local.cc b/test/syscalls/linux/socket_unix_dgram_local.cc deleted file mode 100644 index 31d2d5216..000000000 --- a/test/syscalls/linux/socket_unix_dgram_local.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_stream.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/socket_unix_dgram.h" -#include "test/syscalls/linux/socket_unix_non_stream.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_DGRAM, SOCK_RAW}, - List<int>{0, SOCK_NONBLOCK})))); -} - -INSTANTIATE_TEST_SUITE_P( - DgramUnixSockets, DgramUnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - DgramUnixSockets, UnixNonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - DgramUnixSockets, NonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_dgram_non_blocking.cc b/test/syscalls/linux/socket_unix_dgram_non_blocking.cc deleted file mode 100644 index 2db8b68d3..000000000 --- a/test/syscalls/linux/socket_unix_dgram_non_blocking.cc +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of connected non-blocking dgram -// unix sockets. -using NonBlockingDgramUnixSocketPairTest = SocketPairTest; - -TEST_P(NonBlockingDgramUnixSocketPairTest, ReadOneSideClosed) { - if (IsRunningOnGvisor()) { - // FIXME(b/70803293): gVisor datagram sockets return 0 instead of - // EAGAIN. - return; - } - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - char data[10] = {}; - ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)), - SyscallFailsWithErrno(EAGAIN)); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingDgramUnixSockets, NonBlockingDgramUnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(std::vector<SocketPairKind>{ - UnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK), - FilesystemBoundUnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK), - AbstractBoundUnixDomainSocketPair(SOCK_DGRAM | SOCK_NONBLOCK), - }))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_domain.cc b/test/syscalls/linux/socket_unix_domain.cc deleted file mode 100644 index f7dff8b4d..000000000 --- a/test/syscalls/linux/socket_unix_domain.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_generic.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, AllSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_filesystem_nonblock.cc b/test/syscalls/linux/socket_unix_filesystem_nonblock.cc deleted file mode 100644 index 6700b4d90..000000000 --- a/test/syscalls/linux/socket_unix_filesystem_nonblock.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingFilesystemUnixSockets, NonBlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_non_stream.cc b/test/syscalls/linux/socket_unix_non_stream.cc deleted file mode 100644 index 884319e1d..000000000 --- a/test/syscalls/linux/socket_unix_non_stream.cc +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_unix_non_stream.h" - -#include <stdio.h> -#include <sys/mman.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/memory_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -TEST_P(UnixNonStreamSocketPairTest, RecvMsgTooLarge) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int rcvbuf; - socklen_t length = sizeof(rcvbuf); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, &length), - SyscallSucceeds()); - - // Make the call larger than the receive buffer. - const int recv_size = 3 * rcvbuf; - - // Write a message that does fit in the receive buffer. - const int write_size = rcvbuf - kPageSize; - - std::vector<char> write_buf(write_size, 'a'); - const int ret = RetryEINTR(write)(sockets->second_fd(), write_buf.data(), - write_buf.size()); - if (ret < 0 && errno == ENOBUFS) { - // NOTE(b/116636318): Linux may stall the write for a long time and - // ultimately return ENOBUFS. Allow this error, since a retry will likely - // result in the same error. - return; - } - ASSERT_THAT(ret, SyscallSucceeds()); - - std::vector<char> recv_buf(recv_size); - - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sockets->first_fd(), recv_buf.data(), - recv_buf.size(), write_size)); - - recv_buf.resize(write_size); - EXPECT_EQ(recv_buf, write_buf); -} - -// Create a region of anonymous memory of size 'size', which is fragmented in -// FileMem. -// -// ptr contains the start address of the region. The returned vector contains -// all of the mappings to be unmapped when done. -PosixErrorOr<std::vector<Mapping>> CreateFragmentedRegion(const int size, - void** ptr) { - Mapping region; - ASSIGN_OR_RETURN_ERRNO(region, Mmap(nullptr, size, PROT_NONE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); - - *ptr = region.ptr(); - - // Don't save hundreds of times for all of these mmaps. - DisableSave ds; - - std::vector<Mapping> pages; - - // Map and commit a single page at a time, mapping and committing an unrelated - // page between each call to force FileMem fragmentation. - for (uintptr_t addr = region.addr(); addr < region.endaddr(); - addr += kPageSize) { - Mapping page; - ASSIGN_OR_RETURN_ERRNO( - page, - Mmap(reinterpret_cast<void*>(addr), kPageSize, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0)); - *reinterpret_cast<volatile char*>(page.ptr()) = 42; - - pages.emplace_back(std::move(page)); - - // Unrelated page elsewhere. - ASSIGN_OR_RETURN_ERRNO(page, - Mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); - *reinterpret_cast<volatile char*>(page.ptr()) = 42; - - pages.emplace_back(std::move(page)); - } - - // The mappings above have taken ownership of the region. - region.release(); - - return std::move(pages); -} - -// A contiguous iov that is heavily fragmented in FileMem can still be sent -// successfully. See b/115833655. -TEST_P(UnixNonStreamSocketPairTest, FragmentedSendMsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - const int buffer_size = UIO_MAXIOV * kPageSize; - // Extra page for message header overhead. - const int sndbuf = buffer_size + kPageSize; - // N.B. setsockopt(SO_SNDBUF) doubles the passed value. - const int set_sndbuf = sndbuf / 2; - - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, - &set_sndbuf, sizeof(set_sndbuf)), - SyscallSucceeds()); - - int actual_sndbuf = 0; - socklen_t length = sizeof(actual_sndbuf); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, - &actual_sndbuf, &length), - SyscallSucceeds()); - - if (actual_sndbuf != sndbuf) { - // Unable to get the sndbuf we want. - // - // N.B. At minimum, the socketpair gofer should provide a socket that is - // already the correct size. - // - // TODO(b/35921550): When internal UDS support SO_SNDBUF, we can assert that - // we always get the right SO_SNDBUF on gVisor. - GTEST_SKIP() << "SO_SNDBUF = " << actual_sndbuf << ", want " << sndbuf; - } - - // Create a contiguous region of memory of 2*UIO_MAXIOV*PAGE_SIZE. We'll call - // sendmsg with a single iov, but the goal is to get the sentry to split this - // into > UIO_MAXIOV iovs when calling the kernel. - void* ptr; - std::vector<Mapping> pages = - ASSERT_NO_ERRNO_AND_VALUE(CreateFragmentedRegion(buffer_size, &ptr)); - - struct iovec iov = {}; - iov.iov_base = ptr; - iov.iov_len = buffer_size; - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - // NOTE(b/116636318,b/115833655): Linux has poor behavior in the presence of - // physical memory fragmentation. As a result, this may stall for a long time - // and ultimately return ENOBUFS. Allow this error, since it means that we - // made it to the host kernel and started the sendmsg. - EXPECT_THAT(RetryEINTR(sendmsg)(sockets->first_fd(), &msg, 0), - AnyOf(SyscallSucceedsWithValue(buffer_size), - SyscallFailsWithErrno(ENOBUFS))); -} - -// A contiguous iov that is heavily fragmented in FileMem can still be received -// into successfully. Regression test for b/115833655. -TEST_P(UnixNonStreamSocketPairTest, FragmentedRecvMsg) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - const int buffer_size = UIO_MAXIOV * kPageSize; - // Extra page for message header overhead. - const int sndbuf = buffer_size + kPageSize; - // N.B. setsockopt(SO_SNDBUF) doubles the passed value. - const int set_sndbuf = sndbuf / 2; - - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, - &set_sndbuf, sizeof(set_sndbuf)), - SyscallSucceeds()); - - int actual_sndbuf = 0; - socklen_t length = sizeof(actual_sndbuf); - ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, - &actual_sndbuf, &length), - SyscallSucceeds()); - - if (actual_sndbuf != sndbuf) { - // Unable to get the sndbuf we want. - // - // N.B. At minimum, the socketpair gofer should provide a socket that is - // already the correct size. - // - // TODO(b/35921550): When internal UDS support SO_SNDBUF, we can assert that - // we always get the right SO_SNDBUF on gVisor. - GTEST_SKIP() << "SO_SNDBUF = " << actual_sndbuf << ", want " << sndbuf; - } - - std::vector<char> write_buf(buffer_size, 'a'); - const int ret = RetryEINTR(write)(sockets->first_fd(), write_buf.data(), - write_buf.size()); - if (ret < 0 && errno == ENOBUFS) { - // NOTE(b/116636318): Linux may stall the write for a long time and - // ultimately return ENOBUFS. Allow this error, since a retry will likely - // result in the same error. - return; - } - ASSERT_THAT(ret, SyscallSucceeds()); - - // Create a contiguous region of memory of 2*UIO_MAXIOV*PAGE_SIZE. We'll call - // sendmsg with a single iov, but the goal is to get the sentry to split this - // into > UIO_MAXIOV iovs when calling the kernel. - void* ptr; - std::vector<Mapping> pages = - ASSERT_NO_ERRNO_AND_VALUE(CreateFragmentedRegion(buffer_size, &ptr)); - - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg( - sockets->second_fd(), reinterpret_cast<char*>(ptr), buffer_size)); - - EXPECT_EQ(0, memcmp(write_buf.data(), ptr, buffer_size)); -} - -TEST_P(UnixNonStreamSocketPairTest, SendTimeout) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - const int buf_size = 5 * kPageSize; - EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_SNDBUF, &buf_size, - sizeof(buf_size)), - SyscallSucceeds()); - EXPECT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_RCVBUF, &buf_size, - sizeof(buf_size)), - SyscallSucceeds()); - - // The buffer size should be big enough to avoid many iterations in the next - // loop. Otherwise, this will slow down cooperative_save tests. - std::vector<char> buf(kPageSize); - for (;;) { - int ret; - ASSERT_THAT( - ret = RetryEINTR(send)(sockets->first_fd(), buf.data(), buf.size(), 0), - ::testing::AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EAGAIN))); - if (ret == -1) { - break; - } - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_non_stream.h b/test/syscalls/linux/socket_unix_non_stream.h deleted file mode 100644 index 7478ab172..000000000 --- a/test/syscalls/linux/socket_unix_non_stream.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected non-stream -// unix-domain sockets. -using UnixNonStreamSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_NON_STREAM_H_ diff --git a/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc b/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc deleted file mode 100644 index fddcdf1c5..000000000 --- a/test/syscalls/linux/socket_unix_non_stream_blocking_local.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_stream_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(UnixDomainSocketPair, - std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET}), - ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair, - std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET}), - ApplyVec<SocketPairKind>(AbstractBoundUnixDomainSocketPair, - std::vector<int>{SOCK_DGRAM, SOCK_SEQPACKET})); -} - -INSTANTIATE_TEST_SUITE_P( - BlockingNonStreamUnixSockets, BlockingNonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_pair.cc b/test/syscalls/linux/socket_unix_pair.cc deleted file mode 100644 index 85999db04..000000000 --- a/test/syscalls/linux/socket_unix_pair.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/socket_unix.h" -#include "test/syscalls/linux/socket_unix_cmsg.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>(ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK}))); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnixSocketPairCmsgTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_pair_nonblock.cc b/test/syscalls/linux/socket_unix_pair_nonblock.cc deleted file mode 100644 index 281410a9a..000000000 --- a/test/syscalls/linux/socket_unix_pair_nonblock.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET}, - List<int>{SOCK_NONBLOCK})); -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingUnixSockets, NonBlockingSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_seqpacket.cc b/test/syscalls/linux/socket_unix_seqpacket.cc deleted file mode 100644 index d6e7031c0..000000000 --- a/test/syscalls/linux/socket_unix_seqpacket.cc +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_unix_seqpacket.h" - -#include <stdio.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST_P(SeqpacketUnixSocketPairTest, WriteOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - constexpr char kStr[] = "abc"; - ASSERT_THAT(write(sockets->second_fd(), kStr, 3), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(SeqpacketUnixSocketPairTest, ReadOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - char data[10] = {}; - ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(SeqpacketUnixSocketPairTest, Sendto) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - constexpr char kPath[] = "\0nonexistent"; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - constexpr char kStr[] = "abc"; - ASSERT_THAT(sendto(sockets->second_fd(), kStr, 3, 0, (struct sockaddr*)&addr, - sizeof(addr)), - SyscallSucceedsWithValue(3)); - - char data[10] = {}; - ASSERT_THAT(read(sockets->first_fd(), data, sizeof(data)), - SyscallSucceedsWithValue(3)); -} - -TEST_P(SeqpacketUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int sock = sockets->first_fd(); - int buf_size = 0; - socklen_t buf_size_len = sizeof(buf_size); - ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), - SyscallSucceeds()); - int opts; - ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); - - std::vector<char> buf(buf_size / 4); - // Write till the socket buffer is full. - while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { - // Sleep to give linux a chance to move data from the send buffer to the - // receive buffer. - absl::SleepFor(absl::Milliseconds(10)); // 10ms. - } - // The last error should have been EWOULDBLOCK. - ASSERT_EQ(errno, EWOULDBLOCK); - - // Now increase the socket send buffer. - buf_size = buf_size * 2; - ASSERT_THAT( - setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), - SyscallSucceeds()); - - // Skip test if the setsockopt didn't increase the sendbuf. This happens for - // tests where the socket is a host fd where gVisor does not permit increasing - // send buffer size. - int new_buf_size = 0; - buf_size_len = sizeof(new_buf_size); - ASSERT_THAT( - getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &new_buf_size, &buf_size_len), - SyscallSucceeds()); - if (IsRunningOnGvisor() && (new_buf_size <= buf_size)) { - GTEST_SKIP() << "Skipping test new send buffer size " << new_buf_size - << " is the same as the value before setsockopt, " - << " socket is probably a host backed socket." << std ::endl; - } - // send should succeed again. - ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), - SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_seqpacket.h b/test/syscalls/linux/socket_unix_seqpacket.h deleted file mode 100644 index 30d9b9edf..000000000 --- a/test/syscalls/linux/socket_unix_seqpacket.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_ -#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_ - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// Test fixture for tests that apply to pairs of connected seqpacket unix -// sockets. -using SeqpacketUnixSocketPairTest = SocketPairTest; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_SEQPACKET_H_ diff --git a/test/syscalls/linux/socket_unix_seqpacket_local.cc b/test/syscalls/linux/socket_unix_seqpacket_local.cc deleted file mode 100644 index 69a5f150d..000000000 --- a/test/syscalls/linux/socket_unix_seqpacket_local.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_non_stream.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/socket_unix_non_stream.h" -#include "test/syscalls/linux/socket_unix_seqpacket.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})))); -} - -INSTANTIATE_TEST_SUITE_P( - SeqpacketUnixSockets, NonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - SeqpacketUnixSockets, SeqpacketUnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -INSTANTIATE_TEST_SUITE_P( - SeqpacketUnixSockets, UnixNonStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc deleted file mode 100644 index 3ff810914..000000000 --- a/test/syscalls/linux/socket_unix_stream.cc +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <poll.h> -#include <stdio.h> -#include <sys/un.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of connected stream unix sockets. -using StreamUnixSocketPairTest = SocketPairTest; - -TEST_P(StreamUnixSocketPairTest, WriteOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - constexpr char kStr[] = "abc"; - ASSERT_THAT(write(sockets->second_fd(), kStr, 3), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(StreamUnixSocketPairTest, ReadOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - char data[10] = {}; - ASSERT_THAT(read(sockets->second_fd(), data, sizeof(data)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(StreamUnixSocketPairTest, RecvmsgOneSideClosed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Set timeout so that it will not wait for ever. - struct timeval tv { - .tv_sec = 0, .tv_usec = 10 - }; - EXPECT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, - sizeof(tv)), - SyscallSucceeds()); - - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - - char received_data[10] = {}; - struct iovec iov; - iov.iov_base = received_data; - iov.iov_len = sizeof(received_data); - struct msghdr msg = {}; - msg.msg_flags = -1; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(recvmsg(sockets->second_fd(), &msg, MSG_WAITALL), - SyscallSucceedsWithValue(0)); -} - -TEST_P(StreamUnixSocketPairTest, ReadOneSideClosedWithUnreadData) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char buf[10] = {}; - ASSERT_THAT(RetryEINTR(write)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)), - SyscallSucceedsWithValue(0)); - - ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); -} - -TEST_P(StreamUnixSocketPairTest, Sendto) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - constexpr char kPath[] = "\0nonexistent"; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - constexpr char kStr[] = "abc"; - ASSERT_THAT(sendto(sockets->second_fd(), kStr, 3, 0, (struct sockaddr*)&addr, - sizeof(addr)), - SyscallFailsWithErrno(EISCONN)); -} - -TEST_P(StreamUnixSocketPairTest, SetAndGetSocketLinger) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct linger sl = {1, 5}; - EXPECT_THAT( - setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceedsWithValue(0)); - - struct linger got_linger = {}; - socklen_t length = sizeof(sl); - EXPECT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, - &got_linger, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got_linger)); - EXPECT_EQ(0, memcmp(&got_linger, &sl, length)); -} - -TEST_P(StreamUnixSocketPairTest, GetSocketAcceptConn) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceedsWithValue(0)); - - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} - -TEST_P(StreamUnixSocketPairTest, SetSocketSendBuf) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - auto s = sockets->first_fd(); - int max = 0; - int min = 0; - { - // Discover maxmimum buffer size by setting to a really large value. - constexpr int kRcvBufSz = INT_MAX; - ASSERT_THAT( - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - max = 0; - socklen_t max_len = sizeof(max); - ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &max, &max_len), - SyscallSucceeds()); - } - - { - // Discover minimum buffer size by setting it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT( - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &kRcvBufSz, sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &min, &min_len), - SyscallSucceeds()); - } - - int quarter_sz = min + (max - min) / 4; - ASSERT_THAT( - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &quarter_sz, sizeof(quarter_sz)), - SyscallSucceeds()); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &val, &val_len), - SyscallSucceeds()); - - // Linux doubles the value set by SO_SNDBUF/SO_SNDBUF. - quarter_sz *= 2; - ASSERT_EQ(quarter_sz, val); -} - -TEST_P(StreamUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int sock = sockets->first_fd(); - int buf_size = 0; - socklen_t buf_size_len = sizeof(buf_size); - ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), - SyscallSucceeds()); - int opts; - ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); - - std::vector<char> buf(buf_size / 4); - // Write till the socket buffer is full. - while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { - // Sleep to give linux a chance to move data from the send buffer to the - // receive buffer. - absl::SleepFor(absl::Milliseconds(10)); // 10ms. - } - // The last error should have been EWOULDBLOCK. - ASSERT_EQ(errno, EWOULDBLOCK); - - // Now increase the socket send buffer. - buf_size = buf_size * 2; - ASSERT_THAT( - setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), - SyscallSucceeds()); - - // The send should succeed again. - ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), - SyscallSucceeds()); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, StreamUnixSocketPairTest, - ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK})))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_stream_blocking_local.cc b/test/syscalls/linux/socket_unix_stream_blocking_local.cc deleted file mode 100644 index 8429bd429..000000000 --- a/test/syscalls/linux/socket_unix_stream_blocking_local.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_stream_blocking.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - UnixDomainSocketPair(SOCK_STREAM), - FilesystemBoundUnixDomainSocketPair(SOCK_STREAM), - AbstractBoundUnixDomainSocketPair(SOCK_STREAM), - }; -} - -INSTANTIATE_TEST_SUITE_P( - BlockingStreamUnixSockets, BlockingStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_stream_local.cc b/test/syscalls/linux/socket_unix_stream_local.cc deleted file mode 100644 index a7e3449a9..000000000 --- a/test/syscalls/linux/socket_unix_stream_local.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <vector> - -#include "test/syscalls/linux/socket_stream.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK}))); -} - -INSTANTIATE_TEST_SUITE_P( - StreamUnixSockets, StreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_stream_nonblock_local.cc b/test/syscalls/linux/socket_unix_stream_nonblock_local.cc deleted file mode 100644 index 4b763c8e2..000000000 --- a/test/syscalls/linux/socket_unix_stream_nonblock_local.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include <vector> - -#include "test/syscalls/linux/socket_stream_nonblock.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -std::vector<SocketPairKind> GetSocketPairs() { - return { - UnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK), - FilesystemBoundUnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK), - AbstractBoundUnixDomainSocketPair(SOCK_STREAM | SOCK_NONBLOCK), - }; -} - -INSTANTIATE_TEST_SUITE_P( - NonBlockingStreamUnixSockets, NonBlockingStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(GetSocketPairs()))); - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_abstract.cc b/test/syscalls/linux/socket_unix_unbound_abstract.cc deleted file mode 100644 index 8b1762000..000000000 --- a/test/syscalls/linux/socket_unix_unbound_abstract.cc +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of unbound abstract unix sockets. -using UnboundAbstractUnixSocketPairTest = SocketPairTest; - -TEST_P(UnboundAbstractUnixSocketPairTest, AddressAfterNull) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = - *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr()); - ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0); - SKIP_IF(addr.sun_path[sizeof(addr.sun_path) - 2] != 0 || - addr.sun_path[sizeof(addr.sun_path) - 3] != 0); - - addr.sun_path[sizeof(addr.sun_path) - 2] = 'a'; - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), - reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceeds()); -} - -TEST_P(UnboundAbstractUnixSocketPairTest, ShortAddressNotExtended) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = - *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr()); - ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size() - 1), - SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(UnboundAbstractUnixSocketPairTest, BindNothing) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - ASSERT_THAT(bind(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceeds()); -} - -TEST_P(UnboundAbstractUnixSocketPairTest, GetSockNameFullLength) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - sockaddr_storage addr = {}; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT(getsockname(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, sockets->first_addr_size()); -} - -TEST_P(UnboundAbstractUnixSocketPairTest, GetSockNamePartialLength) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size() - 1), - SyscallSucceeds()); - - sockaddr_storage addr = {}; - socklen_t addr_len = sizeof(addr); - ASSERT_THAT(getsockname(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&addr), &addr_len), - SyscallSucceeds()); - EXPECT_EQ(addr_len, sockets->first_addr_size() - 1); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnboundAbstractUnixSocketPairTest, - ::testing::ValuesIn(ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET, - SOCK_DGRAM}, - List<int>{0, SOCK_NONBLOCK})))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_dgram.cc b/test/syscalls/linux/socket_unix_unbound_dgram.cc deleted file mode 100644 index 907dca0f1..000000000 --- a/test/syscalls/linux/socket_unix_unbound_dgram.cc +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdio.h> -#include <sys/socket.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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of unbound dgram unix sockets. -using UnboundDgramUnixSocketPairTest = SocketPairTest; - -TEST_P(UnboundDgramUnixSocketPairTest, BindConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(UnboundDgramUnixSocketPairTest, SelfConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(UnboundDgramUnixSocketPairTest, DoubleConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); -} - -TEST_P(UnboundDgramUnixSocketPairTest, GetRemoteAddress) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - socklen_t addressLength = sockets->first_addr_size(); - struct sockaddr_storage address = {}; - ASSERT_THAT(getpeername(sockets->second_fd(), (struct sockaddr*)(&address), - &addressLength), - SyscallSucceeds()); - EXPECT_EQ( - 0, memcmp(&address, sockets->first_addr(), sockets->first_addr_size())); -} - -TEST_P(UnboundDgramUnixSocketPairTest, Sendto) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(sendto(sockets->second_fd(), sent_data, sizeof(sent_data), 0, - sockets->first_addr(), sockets->first_addr_size()), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data)]; - ASSERT_THAT(ReadFd(sockets->first_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); -} - -TEST_P(UnboundDgramUnixSocketPairTest, ZeroWriteAllowed) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - char sent_data[3]; - // Send a zero length packet. - ASSERT_THAT(write(sockets->second_fd(), sent_data, 0), - SyscallSucceedsWithValue(0)); - // Receive the packet. - char received_data[sizeof(sent_data)]; - ASSERT_THAT(read(sockets->first_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(UnboundDgramUnixSocketPairTest, Listen) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(listen(sockets->first_fd(), 0), SyscallFailsWithErrno(ENOTSUP)); -} - -TEST_P(UnboundDgramUnixSocketPairTest, Accept) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - ASSERT_THAT(accept(sockets->first_fd(), nullptr, nullptr), - SyscallFailsWithErrno(ENOTSUP)); -} - -TEST_P(UnboundDgramUnixSocketPairTest, SendtoWithoutConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - char data = 'a'; - ASSERT_THAT( - RetryEINTR(sendto)(sockets->second_fd(), &data, sizeof(data), 0, - sockets->first_addr(), sockets->first_addr_size()), - SyscallSucceedsWithValue(sizeof(data))); -} - -TEST_P(UnboundDgramUnixSocketPairTest, SendtoWithoutConnectPassCreds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - SetSoPassCred(sockets->first_fd()); - char data = 'a'; - ASSERT_THAT( - RetryEINTR(sendto)(sockets->second_fd(), &data, sizeof(data), 0, - sockets->first_addr(), sockets->first_addr_size()), - SyscallSucceedsWithValue(sizeof(data))); - ucred creds; - creds.pid = -1; - char buf[sizeof(data) + 1]; - ASSERT_NO_FATAL_FAILURE( - RecvCreds(sockets->first_fd(), &creds, buf, sizeof(buf), sizeof(data))); - EXPECT_EQ(0, memcmp(&data, buf, sizeof(data))); - EXPECT_THAT(getpid(), SyscallSucceedsWithValue(creds.pid)); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnboundDgramUnixSocketPairTest, - ::testing::ValuesIn(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_DGRAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_DGRAM}, - List<int>{0, SOCK_NONBLOCK}))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_filesystem.cc b/test/syscalls/linux/socket_unix_unbound_filesystem.cc deleted file mode 100644 index a035fb095..000000000 --- a/test/syscalls/linux/socket_unix_unbound_filesystem.cc +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <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 { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of unbound filesystem unix -// sockets. -using UnboundFilesystemUnixSocketPairTest = SocketPairTest; - -TEST_P(UnboundFilesystemUnixSocketPairTest, AddressAfterNull) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - struct sockaddr_un addr = - *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr()); - ASSERT_EQ(addr.sun_path[sizeof(addr.sun_path) - 1], 0); - SKIP_IF(addr.sun_path[sizeof(addr.sun_path) - 2] != 0 || - addr.sun_path[sizeof(addr.sun_path) - 3] != 0); - - addr.sun_path[sizeof(addr.sun_path) - 2] = 'a'; - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - ASSERT_THAT(bind(sockets->second_fd(), - reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(UnboundFilesystemUnixSocketPairTest, GetSockNameLength) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - sockaddr_storage got_addr = {}; - socklen_t got_addr_len = sizeof(got_addr); - ASSERT_THAT( - getsockname(sockets->first_fd(), - reinterpret_cast<struct sockaddr*>(&got_addr), &got_addr_len), - SyscallSucceeds()); - - sockaddr_un want_addr = - *reinterpret_cast<const struct sockaddr_un*>(sockets->first_addr()); - - EXPECT_EQ(got_addr_len, - 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>( - FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM, SOCK_SEQPACKET, - SOCK_DGRAM}, - List<int>{0, SOCK_NONBLOCK})))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_seqpacket.cc b/test/syscalls/linux/socket_unix_unbound_seqpacket.cc deleted file mode 100644 index cb99030f5..000000000 --- a/test/syscalls/linux/socket_unix_unbound_seqpacket.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of unbound seqpacket unix sockets. -using UnboundUnixSeqpacketSocketPairTest = SocketPairTest; - -TEST_P(UnboundUnixSeqpacketSocketPairTest, SendtoWithoutConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - char data = 'a'; - ASSERT_THAT(sendto(sockets->second_fd(), &data, sizeof(data), 0, - sockets->first_addr(), sockets->first_addr_size()), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(UnboundUnixSeqpacketSocketPairTest, SendtoWithoutConnectIgnoresAddr) { - // FIXME(b/68223466): gVisor tries to find /foo/bar and thus returns ENOENT. - if (IsRunningOnGvisor()) { - return; - } - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - // Even a bogus address is completely ignored. - constexpr char kPath[] = "/foo/bar"; - - // Sanity check that kPath doesn't exist. - struct stat s; - ASSERT_THAT(stat(kPath, &s), SyscallFailsWithErrno(ENOENT)); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - char data = 'a'; - ASSERT_THAT( - sendto(sockets->second_fd(), &data, sizeof(data), 0, - reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(ENOTCONN)); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnboundUnixSeqpacketSocketPairTest, - ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>( - FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_SEQPACKET}, - List<int>{0, SOCK_NONBLOCK})))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_stream.cc b/test/syscalls/linux/socket_unix_unbound_stream.cc deleted file mode 100644 index f185dded3..000000000 --- a/test/syscalls/linux/socket_unix_unbound_stream.cc +++ /dev/null @@ -1,733 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <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/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Test fixture for tests that apply to pairs of connected unix stream sockets. -using UnixStreamSocketPairTest = SocketPairTest; - -// FDPassPartialRead checks that sent control messages cannot be read after -// any of their associated data has been read while ignoring the control message -// by using read(2) instead of recvmsg(2). -TEST_P(UnixStreamSocketPairTest, FDPassPartialRead) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data))); - - char received_data[sizeof(sent_data) / 2]; - ASSERT_THAT( - ReadFd(sockets->second_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(received_data))); - - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)); - EXPECT_EQ(0, memcmp(sent_data + sizeof(received_data), received_data, - sizeof(received_data))); -} - -TEST_P(UnixStreamSocketPairTest, FDPassCoalescedRead) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(), - sent_data1, sizeof(sent_data1))); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(), - sent_data2, sizeof(sent_data2))); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - ASSERT_THAT( - ReadFd(sockets->second_fd(), received_data, sizeof(received_data)), - SyscallSucceedsWithValue(sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); -} - -// ZeroLengthMessageFDDiscarded checks that control messages associated with -// zero length messages are discarded. -TEST_P(UnixStreamSocketPairTest, ZeroLengthMessageFDDiscarded) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - // Zero length arrays are invalid in ISO C++, so allocate one of size 1 and - // send a length of 0. - char sent_data1[1] = {}; - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE( - SendSingleFD(sockets->first_fd(), pair->second_fd(), sent_data1, 0)); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - char received_data[sizeof(sent_data2)] = {}; - - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data)); - EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(received_data))); -} - -// FDPassCoalescedRecv checks that control messages not in the first message are -// preserved in a coalesced recv. -TEST_P(UnixStreamSocketPairTest, FDPassCoalescedRecv) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data, sizeof(sent_data) / 2), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data + sizeof(sent_data) / 2, - sizeof(sent_data) / 2)); - - char received_data[sizeof(sent_data)]; - - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); -} - -// ReadsNotCoalescedAfterFDPass checks that messages after a message containing -// an FD control message are not coalesced. -TEST_P(UnixStreamSocketPairTest, ReadsNotCoalescedAfterFDPass) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair->second_fd(), - sent_data, sizeof(sent_data) / 2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data + sizeof(sent_data) / 2, - sizeof(sent_data) / 2), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - char received_data[sizeof(sent_data)]; - - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data), - sizeof(sent_data) / 2)); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair->first_fd())); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(sent_data) / 2)); - - EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data, - sizeof(sent_data) / 2)); -} - -// FDPassNotCombined checks that FD control messages are not combined in a -// coalesced read. -TEST_P(UnixStreamSocketPairTest, FDPassNotCombined) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - auto pair1 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair1->second_fd(), - sent_data, sizeof(sent_data) / 2)); - - auto pair2 = - ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_SEQPACKET).Create()); - - ASSERT_NO_FATAL_FAILURE(SendSingleFD(sockets->first_fd(), pair2->second_fd(), - sent_data + sizeof(sent_data) / 2, - sizeof(sent_data) / 2)); - - char received_data[sizeof(sent_data)]; - - int fd = -1; - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data), - sizeof(sent_data) / 2)); - - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair1->first_fd())); - - EXPECT_THAT(close(fd), SyscallSucceeds()); - fd = -1; - - ASSERT_NO_FATAL_FAILURE(RecvSingleFD(sockets->second_fd(), &fd, received_data, - sizeof(received_data), - sizeof(sent_data) / 2)); - - EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data, - sizeof(sent_data) / 2)); - - ASSERT_NO_FATAL_FAILURE(TransferTest(fd, pair2->first_fd())); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_P(UnixStreamSocketPairTest, CredPassPartialRead) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data[20]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - - struct ucred sent_creds; - - ASSERT_THAT(sent_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(sent_creds.gid = getgid(), SyscallSucceeds()); - - ASSERT_NO_FATAL_FAILURE( - SendCreds(sockets->first_fd(), sent_creds, sent_data, sizeof(sent_data))); - - int one = 1; - ASSERT_THAT(setsockopt(sockets->second_fd(), SOL_SOCKET, SO_PASSCRED, &one, - sizeof(one)), - SyscallSucceeds()); - - for (int i = 0; i < 2; i++) { - char received_data[10]; - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data), - sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data + i * sizeof(received_data), received_data, - sizeof(received_data))); - EXPECT_EQ(sent_creds.pid, received_creds.pid); - EXPECT_EQ(sent_creds.uid, received_creds.uid); - EXPECT_EQ(sent_creds.gid, received_creds.gid); - } -} - -// Unix stream sockets peek in the same way as datagram sockets. -// -// SinglePeek checks that only a single message is peekable in a single recv. -TEST_P(UnixStreamSocketPairTest, SinglePeek) { - if (!IsRunningOnGvisor()) { - // Don't run this test on linux kernels newer than 4.3.x Linux kernel commit - // 9f389e35674f5b086edd70ed524ca0f287259725 which changes this behavior. We - // used to target 3.11 compatibility, so disable this test on newer kernels. - // - // NOTE(b/118902768): Bring this up to Linux 4.4 compatibility. - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); - SKIP_IF(version.major > 4 || (version.major == 4 && version.minor >= 3)); - } - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - char sent_data[40]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), sent_data, - sizeof(sent_data) / 2, 0), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - ASSERT_THAT( - RetryEINTR(send)(sockets->first_fd(), sent_data + sizeof(sent_data) / 2, - sizeof(sent_data) / 2, 0), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - char received_data[sizeof(sent_data)]; - for (int i = 0; i < 3; i++) { - memset(received_data, 0, sizeof(received_data)); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(received_data), MSG_PEEK), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); - } - memset(received_data, 0, sizeof(received_data)); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(sent_data) / 2, 0), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); - memset(received_data, 0, sizeof(received_data)); - ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), received_data, - sizeof(sent_data) / 2, 0), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - EXPECT_EQ(0, memcmp(sent_data + sizeof(sent_data) / 2, received_data, - sizeof(sent_data) / 2)); -} - -TEST_P(UnixStreamSocketPairTest, CredsNotCoalescedUp) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - SetSoPassCred(sockets->second_fd()); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - - struct ucred received_creds; - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data), - sizeof(sent_data1))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - - struct ucred want_creds { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data), - sizeof(sent_data2))); - - EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2))); - - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixStreamSocketPairTest, CredsNotCoalescedDown) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - UnsetSoPassCred(sockets->second_fd()); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data), - sizeof(sent_data1))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data), - sizeof(sent_data2))); - - EXPECT_EQ(0, memcmp(sent_data2, received_data, sizeof(sent_data2))); - - want_creds = {0, 65534, 65534}; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixStreamSocketPairTest, CoalescedCredsNoPasscred) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - UnsetSoPassCred(sockets->second_fd()); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); -} - -TEST_P(UnixStreamSocketPairTest, CoalescedCreds1) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - SetSoPassCred(sockets->second_fd()); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); - - struct ucred want_creds { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixStreamSocketPairTest, CoalescedCreds2) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds, - received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); - - struct ucred want_creds; - ASSERT_THAT(want_creds.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds.pid, received_creds.pid); - EXPECT_EQ(want_creds.uid, received_creds.uid); - EXPECT_EQ(want_creds.gid, received_creds.gid); -} - -TEST_P(UnixStreamSocketPairTest, NonCoalescedDifferingCreds1) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - SetSoPassCred(sockets->second_fd()); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - char received_data1[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds1; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds1, - received_data1, sizeof(sent_data1))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1))); - - struct ucred want_creds1 { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds1.pid, received_creds1.pid); - EXPECT_EQ(want_creds1.uid, received_creds1.uid); - EXPECT_EQ(want_creds1.gid, received_creds1.gid); - - char received_data2[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds2; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds2, - received_data2, sizeof(sent_data2))); - - EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2))); - - struct ucred want_creds2; - ASSERT_THAT(want_creds2.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds2.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds2.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds2.pid, received_creds2.pid); - EXPECT_EQ(want_creds2.uid, received_creds2.uid); - EXPECT_EQ(want_creds2.gid, received_creds2.gid); -} - -TEST_P(UnixStreamSocketPairTest, NonCoalescedDifferingCreds2) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - UnsetSoPassCred(sockets->second_fd()); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - SetSoPassCred(sockets->second_fd()); - - char received_data1[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds1; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds1, - received_data1, sizeof(sent_data1))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data1, sizeof(sent_data1))); - - struct ucred want_creds1; - ASSERT_THAT(want_creds1.pid = getpid(), SyscallSucceeds()); - ASSERT_THAT(want_creds1.uid = getuid(), SyscallSucceeds()); - ASSERT_THAT(want_creds1.gid = getgid(), SyscallSucceeds()); - - EXPECT_EQ(want_creds1.pid, received_creds1.pid); - EXPECT_EQ(want_creds1.uid, received_creds1.uid); - EXPECT_EQ(want_creds1.gid, received_creds1.gid); - - char received_data2[sizeof(sent_data1) + sizeof(sent_data2)]; - struct ucred received_creds2; - - ASSERT_NO_FATAL_FAILURE(RecvCreds(sockets->second_fd(), &received_creds2, - received_data2, sizeof(sent_data2))); - - EXPECT_EQ(0, memcmp(sent_data2, received_data2, sizeof(sent_data2))); - - struct ucred want_creds2 { - 0, 65534, 65534 - }; - - EXPECT_EQ(want_creds2.pid, received_creds2.pid); - EXPECT_EQ(want_creds2.uid, received_creds2.uid); - EXPECT_EQ(want_creds2.gid, received_creds2.gid); -} - -TEST_P(UnixStreamSocketPairTest, CoalescedDifferingCreds) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - SetSoPassCred(sockets->second_fd()); - - char sent_data1[20]; - RandomizeBuffer(sent_data1, sizeof(sent_data1)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data1, sizeof(sent_data1)), - SyscallSucceedsWithValue(sizeof(sent_data1))); - - char sent_data2[20]; - RandomizeBuffer(sent_data2, sizeof(sent_data2)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data2, sizeof(sent_data2)), - SyscallSucceedsWithValue(sizeof(sent_data2))); - - UnsetSoPassCred(sockets->second_fd()); - - char sent_data3[20]; - RandomizeBuffer(sent_data3, sizeof(sent_data3)); - - ASSERT_THAT(WriteFd(sockets->first_fd(), sent_data3, sizeof(sent_data3)), - SyscallSucceedsWithValue(sizeof(sent_data3))); - - char received_data[sizeof(sent_data1) + sizeof(sent_data2) + - sizeof(sent_data3)]; - - ASSERT_NO_FATAL_FAILURE( - RecvNoCmsg(sockets->second_fd(), received_data, sizeof(received_data))); - - EXPECT_EQ(0, memcmp(sent_data1, received_data, sizeof(sent_data1))); - EXPECT_EQ(0, memcmp(sent_data2, received_data + sizeof(sent_data1), - sizeof(sent_data2))); - EXPECT_EQ(0, memcmp(sent_data3, - received_data + sizeof(sent_data1) + sizeof(sent_data2), - sizeof(sent_data3))); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnixStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(UnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>(FilesystemBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractBoundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK})))))); - -// Test fixture for tests that apply to pairs of unbound unix stream sockets. -using UnboundUnixStreamSocketPairTest = SocketPairTest; - -TEST_P(UnboundUnixStreamSocketPairTest, SendtoWithoutConnect) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - char data = 'a'; - ASSERT_THAT(sendto(sockets->second_fd(), &data, sizeof(data), 0, - sockets->first_addr(), sockets->first_addr_size()), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -TEST_P(UnboundUnixStreamSocketPairTest, SendtoWithoutConnectIgnoresAddr) { - // FIXME(b/68223466): gVisor tries to find /foo/bar and thus returns ENOENT. - if (IsRunningOnGvisor()) { - return; - } - - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - - ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), - sockets->first_addr_size()), - SyscallSucceeds()); - - // Even a bogus address is completely ignored. - constexpr char kPath[] = "/foo/bar"; - - // Sanity check that kPath doesn't exist. - struct stat s; - ASSERT_THAT(stat(kPath, &s), SyscallFailsWithErrno(ENOENT)); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, kPath, sizeof(kPath)); - - char data = 'a'; - ASSERT_THAT( - sendto(sockets->second_fd(), &data, sizeof(data), 0, - reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -INSTANTIATE_TEST_SUITE_P( - AllUnixDomainSockets, UnboundUnixStreamSocketPairTest, - ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( - ApplyVec<SocketPairKind>(FilesystemUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{ - 0, SOCK_NONBLOCK})), - ApplyVec<SocketPairKind>( - AbstractUnboundUnixDomainSocketPair, - AllBitwiseCombinations(List<int>{SOCK_STREAM}, - List<int>{0, SOCK_NONBLOCK})))))); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc deleted file mode 100644 index e5730a606..000000000 --- a/test/syscalls/linux/splice.cc +++ /dev/null @@ -1,939 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/unistd.h> -#include <sys/eventfd.h> -#include <sys/resource.h> -#include <sys/sendfile.h> -#include <sys/time.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/signal_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SpliceTest, TwoRegularFiles) { - // Create temp files. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Open the input file as read only. - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open the output file as write only. - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY)); - - // Verify that it is rejected as expected; regardless of offsets. - loff_t in_offset = 0; - loff_t out_offset = 0; - EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), &in_offset, out_fd.get(), nullptr, 1, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(splice(in_fd.get(), nullptr, out_fd.get(), nullptr, 1, 0), - SyscallFailsWithErrno(EINVAL)); -} - -int memfd_create(const std::string& name, unsigned int flags) { - return syscall(__NR_memfd_create, name.c_str(), flags); -} - -TEST(SpliceTest, NegativeOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file as write only. - int fd; - EXPECT_THAT(fd = memfd_create("negative", 0), SyscallSucceeds()); - const FileDescriptor out_fd(fd); - - loff_t out_offset = 0xffffffffffffffffull; - constexpr int kSize = 2; - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Write offset + size overflows int64. -// -// This is a regression test for b/148041624. -TEST(SpliceTest, WriteOverflow) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file. - int fd; - EXPECT_THAT(fd = memfd_create("overflow", 0), SyscallSucceeds()); - const FileDescriptor out_fd(fd); - - // out_offset + kSize overflows INT64_MAX. - loff_t out_offset = 0x7ffffffffffffffeull; - constexpr int kSize = 3; - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, SamePipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to splice to itself. - EXPECT_THAT(splice(rfd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(TeeTest, SamePipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill the pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to tee to itself. - EXPECT_THAT(tee(rfd.get(), wfd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(TeeTest, RegularFile) { - // Open some file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Attempt to tee from the file. - EXPECT_THAT(tee(in_fd.get(), wfd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(tee(rfd.get(), in_fd.get(), kPageSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, PipeOffsets) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // All pipe offsets should be rejected. - loff_t in_offset = 0; - loff_t out_offset = 0; - EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), &out_offset, 1, 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(splice(rfd1.get(), &in_offset, wfd2.get(), nullptr, 1, 0), - SyscallFailsWithErrno(ESPIPE)); -} - -// Event FDs may be used with splice without an offset. -TEST(SpliceTest, FromEventFD) { - // Open the input eventfd with an initial value so that it is readable. - constexpr uint64_t kEventFDValue = 1; - int efd; - ASSERT_THAT(efd = eventfd(kEventFDValue, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice 8-byte eventfd value to pipe. - constexpr int kEventFDSize = 8; - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kEventFDSize, 0), - SyscallSucceedsWithValue(kEventFDSize)); - - // Contents should be equal. - std::vector<char> rbuf(kEventFDSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - EXPECT_EQ(memcmp(rbuf.data(), &kEventFDValue, rbuf.size()), 0); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, FromEventFDOffset) { - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pread. - constexpr int kEventFDSize = 8; - loff_t in_off = 0; - EXPECT_THAT(splice(in_fd.get(), &in_off, wfd.get(), nullptr, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, ToEventFDOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with a value. - constexpr int kEventFDSize = 8; - std::vector<char> buf(kEventFDSize); - buf[0] = 1; - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor out_fd(efd); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pwrite. - loff_t out_off = 0; - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), &out_off, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SpliceTest, ToPipe) { - // Open the input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(lseek(in_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice to the pipe. - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, ToPipeEOF) { - // Create and open an empty input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice from the empty file to the pipe. - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, 123, 0), - SyscallSucceedsWithValue(0)); -} - -TEST(SpliceTest, ToPipeOffset) { - // Open the input file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor in_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(in_fd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice to the pipe. - loff_t in_offset = kPageSize / 2; - EXPECT_THAT( - splice(in_fd.get(), &in_offset, wfd.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Contents should be equal to only the second part. - std::vector<char> rbuf(kPageSize / 2); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize / 2)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data() + (kPageSize / 2), rbuf.size()), 0); -} - -TEST(SpliceTest, FromPipe) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the output file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice to the output file. - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // The offset of the output should be equal to kPageSize. We assert that and - // reset to zero so that we can read the contents and ensure they match. - EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_CUR), - SyscallSucceedsWithValue(kPageSize)); - ASSERT_THAT(lseek(out_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeMultiple) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - std::string buf = "abcABC123"; - ASSERT_THAT(write(wfd.get(), buf.c_str(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Open the output file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice from the pipe to the output file over several calls. - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - EXPECT_THAT(splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3, 0), - SyscallSucceedsWithValue(3)); - - // Reset cursor to zero so that we can check the contents. - ASSERT_THAT(lseek(out_fd.get(), 0, SEEK_SET), SyscallSucceedsWithValue(0)); - - // Contents should be equal. - std::vector<char> rbuf(buf.size()); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), buf.c_str(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the input file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - // Splice to the output file. - loff_t out_offset = kPageSize / 2; - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), &out_offset, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Content should reflect the splice. We write to a specific offset in the - // file, so the internals should now be allocated sparsely. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(out_fd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - std::vector<char> zbuf(kPageSize / 2); - memset(zbuf.data(), 0, zbuf.size()); - EXPECT_EQ(memcmp(rbuf.data(), zbuf.data(), zbuf.size()), 0); - EXPECT_EQ(memcmp(rbuf.data() + kPageSize / 2, buf.data(), kPageSize / 2), 0); -} - -TEST(SpliceTest, TwoPipes) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Splice to the second pipe, using two operations. - EXPECT_THAT( - splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - EXPECT_THAT( - splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize / 2, 0), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Content should reflect the splice. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(SpliceTest, TwoPipesPartialRead) { - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // Write half a page of data to the first pipe. - std::vector<char> buf(kPageSize / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Attempt to splice one page from the first pipe to the second; it should - // immediately return after splicing the half-page previously written to the - // first pipe. - EXPECT_THAT( - splice(first_rfd.get(), nullptr, second_wfd.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize / 2)); -} - -TEST(SpliceTest, TwoPipesPartialWrite) { - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // Write two pages of data to the first pipe. - std::vector<char> buf(2 * kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(2 * kPageSize)); - - // Limit the second pipe to two pages, then write one page of data to it. - ASSERT_THAT(fcntl(second_wfd.get(), F_SETPIPE_SZ, 2 * kPageSize), - SyscallSucceeds()); - ASSERT_THAT(write(second_wfd.get(), buf.data(), buf.size() / 2), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to splice two pages from the first pipe to the second; it should - // immediately return after splicing the first page previously written to the - // first pipe. - EXPECT_THAT(splice(first_rfd.get(), nullptr, second_wfd.get(), nullptr, - 2 * kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); -} - -TEST(TeeTest, TwoPipesPartialRead) { - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // Write half a page of data to the first pipe. - std::vector<char> buf(kPageSize / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize / 2)); - - // Attempt to tee one page from the first pipe to the second; it should - // immediately return after copying the half-page previously written to the - // first pipe. - EXPECT_THAT(tee(first_rfd.get(), second_wfd.get(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize / 2)); -} - -TEST(TeeTest, TwoPipesPartialWrite) { - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // Write two pages of data to the first pipe. - std::vector<char> buf(2 * kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(2 * kPageSize)); - - // Limit the second pipe to two pages, then write one page of data to it. - ASSERT_THAT(fcntl(second_wfd.get(), F_SETPIPE_SZ, 2 * kPageSize), - SyscallSucceeds()); - ASSERT_THAT(write(second_wfd.get(), buf.data(), buf.size() / 2), - SyscallSucceedsWithValue(kPageSize)); - - // Attempt to tee two pages from the first pipe to the second; it should - // immediately return after copying the first page previously written to the - // first pipe. - EXPECT_THAT(tee(first_rfd.get(), second_wfd.get(), 2 * kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); -} - -TEST(SpliceTest, TwoPipesCircular) { - // This test deadlocks the sentry on VFS1 because VFS1 splice ordering is - // based on fs.File.UniqueID, which does not prevent circular ordering between - // e.g. inode-level locks taken by fs.FileOperations. - SKIP_IF(IsRunningWithVFS1()); - - // Create two pipes. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor first_rfd(fds[0]); - const FileDescriptor first_wfd(fds[1]); - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor second_rfd(fds[0]); - const FileDescriptor second_wfd(fds[1]); - - // On Linux, each pipe is normally limited to - // include/linux/pipe_fs_i.h:PIPE_DEF_BUFFERS buffers worth of data. - constexpr size_t PIPE_DEF_BUFFERS = 16; - - // Write some data to each pipe. Below we splice 1 byte at a time between - // pipes, which very quickly causes each byte to be stored in a separate - // buffer, so we must ensure that the total amount of data in the system is <= - // PIPE_DEF_BUFFERS bytes. - std::vector<char> buf(PIPE_DEF_BUFFERS / 2); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - ASSERT_THAT(write(second_wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Have another thread splice from the second pipe to the first, while we - // splice from the first to the second. The test passes if this does not - // deadlock. - const int kIterations = 1000; - DisableSave ds; - ScopedThread t([&]() { - for (int i = 0; i < kIterations; i++) { - ASSERT_THAT( - splice(second_rfd.get(), nullptr, first_wfd.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(1)); - } - }); - for (int i = 0; i < kIterations; i++) { - ASSERT_THAT( - splice(first_rfd.get(), nullptr, second_wfd.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(1)); - } -} - -TEST(SpliceTest, Blocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // This thread writes to the main pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ScopedThread t([&]() { - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - }); - - // Attempt a splice immediately; it should block. - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the splice. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(TeeTest, Blocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // This thread writes to the main pipe. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ScopedThread t([&]() { - ASSERT_THAT(write(wfd1.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - }); - - // Attempt a tee immediately; it should block. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the splice, in both pipes. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); - ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0); -} - -TEST(TeeTest, BlockingWrite) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Make some data available to be read. - std::vector<char> buf1(kPageSize); - RandomizeBuffer(buf1.data(), buf1.size()); - ASSERT_THAT(write(wfd1.get(), buf1.data(), buf1.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Fill up the write pipe's buffer. - int pipe_size = -1; - ASSERT_THAT(pipe_size = fcntl(wfd2.get(), F_GETPIPE_SZ), SyscallSucceeds()); - std::vector<char> buf2(pipe_size); - ASSERT_THAT(write(wfd2.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(pipe_size)); - - ScopedThread t([&]() { - absl::SleepFor(absl::Milliseconds(100)); - ASSERT_THAT(read(rfd2.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(pipe_size)); - }); - - // Attempt a tee immediately; it should block. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, 0), - SyscallSucceedsWithValue(kPageSize)); - - // Thread should be joinable. - t.Join(); - - // Content should reflect the tee. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf1.data(), kPageSize), 0); -} - -TEST(SpliceTest, NonBlocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Splice with no data to back it. - EXPECT_THAT(splice(rfd1.get(), nullptr, wfd2.get(), nullptr, kPageSize, - SPLICE_F_NONBLOCK), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(TeeTest, NonBlocking) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Splice with no data to back it. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), kPageSize, SPLICE_F_NONBLOCK), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(TeeTest, MultiPage) { - // Create two new pipes. - int first[2], second[2]; - ASSERT_THAT(pipe(first), SyscallSucceeds()); - const FileDescriptor rfd1(first[0]); - const FileDescriptor wfd1(first[1]); - ASSERT_THAT(pipe(second), SyscallSucceeds()); - const FileDescriptor rfd2(second[0]); - const FileDescriptor wfd2(second[1]); - - // Make some data available to be read. - std::vector<char> wbuf(8 * kPageSize); - RandomizeBuffer(wbuf.data(), wbuf.size()); - ASSERT_THAT(write(wfd1.get(), wbuf.data(), wbuf.size()), - SyscallSucceedsWithValue(wbuf.size())); - - // Attempt a tee immediately; it should complete. - EXPECT_THAT(tee(rfd1.get(), wfd2.get(), wbuf.size(), 0), - SyscallSucceedsWithValue(wbuf.size())); - - // Content should reflect the tee. - std::vector<char> rbuf(wbuf.size()); - ASSERT_THAT(read(rfd2.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0); - ASSERT_THAT(read(rfd1.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(rbuf.size())); - EXPECT_EQ(memcmp(rbuf.data(), wbuf.data(), rbuf.size()), 0); -} - -TEST(SpliceTest, FromPipeMaxFileSize) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - // Open the input file. - const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor out_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDWR)); - - EXPECT_THAT(ftruncate(out_fd.get(), 13 << 20), SyscallSucceeds()); - EXPECT_THAT(lseek(out_fd.get(), 0, SEEK_END), - SyscallSucceedsWithValue(13 << 20)); - - // Set our file size limit. - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGXFSZ); - TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); - rlimit rlim = {}; - rlim.rlim_cur = rlim.rlim_max = (13 << 20); - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &rlim), SyscallSucceeds()); - - // Splice to the output file. - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), nullptr, 3 * kPageSize, 0), - SyscallFailsWithErrno(EFBIG)); - - // Contents should be equal. - std::vector<char> rbuf(kPageSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); -} - -TEST(SpliceTest, FromPipeToDevZero) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - const FileDescriptor zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - - // Close the write end to prevent blocking below. - wfd.reset(); - - // Splice to /dev/zero. The first call should empty the pipe, and the return - // value should not exceed the number of bytes available for reading. - EXPECT_THAT( - splice(rfd.get(), nullptr, zero.get(), nullptr, kPageSize + 123, 0), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_THAT(splice(rfd.get(), nullptr, zero.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(0)); -} - -static volatile int signaled = 0; -void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } - -TEST(SpliceTest, ToPipeWithSmallCapacityDoesNotSpin_NoRandomSave) { - // Writes to a pipe that are less than PIPE_BUF must be atomic. This test - // creates a pipe with only 128 bytes of capacity (< PIPE_BUF) and checks that - // splicing to the pipe does not spin. See b/170743336. - - // Create a file with one page of data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(buf.data(), buf.size()), - TempPath::kDefaultFileMode)); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - // Create a pipe with size 4096, and fill all but 128 bytes of it. - int p[2]; - ASSERT_THAT(pipe(p), SyscallSucceeds()); - ASSERT_THAT(fcntl(p[1], F_SETPIPE_SZ, kPageSize), SyscallSucceeds()); - const int kWriteSize = kPageSize - 128; - std::vector<char> writeBuf(kWriteSize); - RandomizeBuffer(writeBuf.data(), writeBuf.size()); - ASSERT_THAT(write(p[1], writeBuf.data(), writeBuf.size()), - SyscallSucceedsWithValue(kWriteSize)); - - // Set up signal handler. - struct sigaction sa = {}; - sa.sa_sigaction = SigUsr1Handler; - sa.sa_flags = SA_SIGINFO; - const auto cleanup_sigact = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - // Send SIGUSR1 to this thread in 1 second. - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = SIGUSR1; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(absl::Seconds(1)); - DisableSave ds; // Asserting an EINTR. - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // Now splice the file to the pipe. This should block, but not spin, and - // should return EINTR because it is interrupted by the signal. - EXPECT_THAT(splice(fd.get(), nullptr, p[1], nullptr, kPageSize, 0), - SyscallFailsWithErrno(EINTR)); - - // Alarm should have been handled. - EXPECT_EQ(signaled, 1); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc deleted file mode 100644 index 72f888659..000000000 --- a/test/syscalls/linux/stat.cc +++ /dev/null @@ -1,799 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/match.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/save_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -#ifndef AT_STATX_FORCE_SYNC -#define AT_STATX_FORCE_SYNC 0x2000 -#endif -#ifndef AT_STATX_DONT_SYNC -#define AT_STATX_DONT_SYNC 0x4000 -#endif - -namespace gvisor { -namespace testing { - -namespace { - -class StatTest : public FileTest {}; - -TEST_F(StatTest, FstatatAbs) { - struct stat st; - - // Check that the stat works. - EXPECT_THAT(fstatat(AT_FDCWD, test_file_name_.c_str(), &st, 0), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(st.st_mode)); -} - -TEST_F(StatTest, FstatatEmptyPath) { - struct stat st; - const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - - // Check that the stat works. - EXPECT_THAT(fstatat(fd.get(), "", &st, AT_EMPTY_PATH), SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(st.st_mode)); -} - -TEST_F(StatTest, FstatatRel) { - struct stat st; - int dirfd; - auto filename = std::string(Basename(test_file_name_)); - - // Open the temporary directory read-only. - ASSERT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_RDONLY), - SyscallSucceeds()); - - // Check that the stat works. - EXPECT_THAT(fstatat(dirfd, filename.c_str(), &st, 0), SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(st.st_mode)); - close(dirfd); -} - -TEST_F(StatTest, FstatatSymlink) { - struct stat st; - - // Check that the link is followed. - EXPECT_THAT(fstatat(AT_FDCWD, "/proc/self", &st, 0), SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - EXPECT_FALSE(S_ISLNK(st.st_mode)); - - // Check that the flag works. - EXPECT_THAT(fstatat(AT_FDCWD, "/proc/self", &st, AT_SYMLINK_NOFOLLOW), - SyscallSucceeds()); - EXPECT_TRUE(S_ISLNK(st.st_mode)); - EXPECT_FALSE(S_ISDIR(st.st_mode)); -} - -TEST_F(StatTest, Nlinks) { - // Skip this test if we are testing overlayfs because overlayfs does not - // (intentionally) return the correct nlink value for directories. - // See fs/overlayfs/inode.c:ovl_getattr(). - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); - - TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Directory is initially empty, it should contain 2 links (one from itself, - // one from "."). - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2)); - - // Create a file in the test directory. Files shouldn't increase the link - // count on the base directory. - TempPath file1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path())); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2)); - - // Create subdirectories. This should increase the link count by 1 per - // subdirectory. - TempPath dir1 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path())); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(3)); - TempPath dir2 = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path())); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(4)); - - // Removing directories should reduce the link count. - dir1.reset(); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(3)); - dir2.reset(); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2)); - - // Removing files should have no effect on link count. - file1.reset(); - EXPECT_THAT(Links(basedir.path()), IsPosixErrorOkAndHolds(2)); -} - -TEST_F(StatTest, BlocksIncreaseOnWrite) { - struct stat st; - - // Stat the empty file. - ASSERT_THAT(fstat(test_file_fd_.get(), &st), SyscallSucceeds()); - - const int initial_blocks = st.st_blocks; - - // Write to the file, making sure to exceed the block size. - std::vector<char> buf(2 * st.st_blksize, 'a'); - ASSERT_THAT(write(test_file_fd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Stat the file again, and verify that number of allocated blocks has - // increased. - ASSERT_THAT(fstat(test_file_fd_.get(), &st), SyscallSucceeds()); - EXPECT_GT(st.st_blocks, initial_blocks); -} - -TEST_F(StatTest, PathNotCleaned) { - TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Create a file in the basedir. - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path())); - - // Stating the file directly should succeed. - struct stat buf; - EXPECT_THAT(lstat(file.path().c_str(), &buf), SyscallSucceeds()); - - // Try to stat the file using a directory that does not exist followed by - // "..". If the path is cleaned prior to stating (which it should not be) - // then this will succeed. - const std::string bad_path = JoinPath("/does_not_exist/..", file.path()); - EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOENT)); -} - -TEST_F(StatTest, PathCanContainDotDot) { - TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath subdir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(basedir.path())); - const std::string subdir_name = std::string(Basename(subdir.path())); - - // Create a file in the subdir. - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(subdir.path())); - const std::string file_name = std::string(Basename(file.path())); - - // Stat the file through a path that includes '..' and '.' but still resolves - // to the file. - const std::string good_path = - JoinPath(basedir.path(), subdir_name, "..", subdir_name, ".", file_name); - struct stat buf; - EXPECT_THAT(lstat(good_path.c_str(), &buf), SyscallSucceeds()); -} - -TEST_F(StatTest, PathCanContainEmptyComponent) { - TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Create a file in the basedir. - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path())); - const std::string file_name = std::string(Basename(file.path())); - - // Stat the file through a path that includes an empty component. We have to - // build this ourselves because JoinPath automatically removes empty - // components. - const std::string good_path = absl::StrCat(basedir.path(), "//", file_name); - struct stat buf; - EXPECT_THAT(lstat(good_path.c_str(), &buf), SyscallSucceeds()); -} - -TEST_F(StatTest, TrailingSlashNotCleanedReturnsENOTDIR) { - TempPath basedir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Create a file in the basedir. - TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(basedir.path())); - - // Stat the file with an extra "/" on the end of it. Since file is not a - // directory, this should return ENOTDIR. - const std::string bad_path = absl::StrCat(file.path(), "/"); - struct stat buf; - EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOTDIR)); -} - -TEST_F(StatTest, FstatFileWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - struct stat st; - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); - - // Stat the directory. - ASSERT_THAT(fstat(fd.get(), &st), SyscallSucceeds()); -} - -TEST_F(StatTest, FstatDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - struct stat st; - TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( - Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); - - // Stat the directory. - ASSERT_THAT(fstat(dirfd.get(), &st), SyscallSucceeds()); -} - -// fstatat with an O_PATH fd -TEST_F(StatTest, FstatatDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( - Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - struct stat st = {}; - EXPECT_THAT(fstatat(dirfd.get(), tmpfile.path().c_str(), &st, 0), - SyscallSucceeds()); - EXPECT_FALSE(S_ISDIR(st.st_mode)); - EXPECT_TRUE(S_ISREG(st.st_mode)); -} - -// Test fstatating a symlink directory. -TEST_F(StatTest, FstatatSymlinkDir) { - // Create a directory and symlink to it. - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - const std::string symlink_to_dir = NewTempAbsPath(); - EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()), - SyscallSucceeds()); - auto cleanup = Cleanup([&symlink_to_dir]() { - EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds()); - }); - - // Fstatat the link with AT_SYMLINK_NOFOLLOW should return symlink data. - struct stat st = {}; - EXPECT_THAT( - fstatat(AT_FDCWD, symlink_to_dir.c_str(), &st, AT_SYMLINK_NOFOLLOW), - SyscallSucceeds()); - EXPECT_FALSE(S_ISDIR(st.st_mode)); - EXPECT_TRUE(S_ISLNK(st.st_mode)); - - // Fstatat the link should return dir data. - EXPECT_THAT(fstatat(AT_FDCWD, symlink_to_dir.c_str(), &st, 0), - SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - EXPECT_FALSE(S_ISLNK(st.st_mode)); -} - -// Test fstatating a symlink directory with trailing slash. -TEST_F(StatTest, FstatatSymlinkDirWithTrailingSlash) { - // Create a directory and symlink to it. - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string symlink_to_dir = NewTempAbsPath(); - EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()), - SyscallSucceeds()); - auto cleanup = Cleanup([&symlink_to_dir]() { - EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds()); - }); - - // Fstatat on the symlink with a trailing slash should return the directory - // data. - struct stat st = {}; - EXPECT_THAT( - fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st, 0), - SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - EXPECT_FALSE(S_ISLNK(st.st_mode)); - - // Fstatat on the symlink with a trailing slash with AT_SYMLINK_NOFOLLOW - // should return the directory data. - // Symlink to directory with trailing slash will ignore AT_SYMLINK_NOFOLLOW. - EXPECT_THAT(fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st, - AT_SYMLINK_NOFOLLOW), - SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - EXPECT_FALSE(S_ISLNK(st.st_mode)); -} - -// Test fstatating a symlink directory with a trailing slash -// should return same stat data with fstatating directory. -TEST_F(StatTest, FstatatSymlinkDirWithTrailingSlashSameInode) { - // Create a directory and symlink to it. - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // We are going to assert that the symlink inode id is the same as the linked - // dir's inode id. In order for the inode id to be stable across - // save/restore, it must be kept open. The FileDescriptor type will do that - // for us automatically. - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY)); - - const std::string symlink_to_dir = NewTempAbsPath(); - EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()), - SyscallSucceeds()); - auto cleanup = Cleanup([&symlink_to_dir]() { - EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds()); - }); - - // Fstatat on the symlink with a trailing slash should return the directory - // data. - struct stat st = {}; - EXPECT_THAT(fstatat(AT_FDCWD, absl::StrCat(symlink_to_dir, "/").c_str(), &st, - AT_SYMLINK_NOFOLLOW), - SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - - // Dir and symlink should point to same inode. - struct stat st_dir = {}; - EXPECT_THAT( - fstatat(AT_FDCWD, dir.path().c_str(), &st_dir, AT_SYMLINK_NOFOLLOW), - SyscallSucceeds()); - EXPECT_EQ(st.st_ino, st_dir.st_ino); -} - -TEST_F(StatTest, LeadingDoubleSlash) { - // Create a file, and make sure we can stat it. - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - struct stat st; - ASSERT_THAT(lstat(file.path().c_str(), &st), SyscallSucceeds()); - - // Now add an extra leading slash. - const std::string double_slash_path = absl::StrCat("/", file.path()); - ASSERT_TRUE(absl::StartsWith(double_slash_path, "//")); - - // We should be able to stat the new path, and it should resolve to the same - // file (same device and inode). - struct stat double_slash_st; - ASSERT_THAT(lstat(double_slash_path.c_str(), &double_slash_st), - SyscallSucceeds()); - EXPECT_EQ(st.st_dev, double_slash_st.st_dev); - // Inode numbers for gofer-accessed files may change across save/restore. - if (!IsRunningWithSaveRestore()) { - EXPECT_EQ(st.st_ino, double_slash_st.st_ino); - } -} - -// Test that a rename doesn't change the underlying file. -TEST_F(StatTest, StatDoesntChangeAfterRename) { - const TempPath old_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const TempPath new_path(NewTempAbsPath()); - - struct stat st_old = {}; - struct stat st_new = {}; - - ASSERT_THAT(stat(old_file.path().c_str(), &st_old), SyscallSucceeds()); - ASSERT_THAT(rename(old_file.path().c_str(), new_path.path().c_str()), - SyscallSucceeds()); - ASSERT_THAT(stat(new_path.path().c_str(), &st_new), SyscallSucceeds()); - - EXPECT_EQ(st_old.st_nlink, st_new.st_nlink); - EXPECT_EQ(st_old.st_dev, st_new.st_dev); - // Inode numbers for gofer-accessed files on which no reference is held may - // change across save/restore because the information that the gofer client - // uses to track file identity (9P QID path) is inconsistent between gofer - // processes, which are restarted across save/restore. - // - // Overlay filesystems may synthesize directory inode numbers on the fly. - if (!IsRunningWithSaveRestore() && - !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) { - EXPECT_EQ(st_old.st_ino, st_new.st_ino); - } - EXPECT_EQ(st_old.st_mode, st_new.st_mode); - EXPECT_EQ(st_old.st_uid, st_new.st_uid); - EXPECT_EQ(st_old.st_gid, st_new.st_gid); - EXPECT_EQ(st_old.st_size, st_new.st_size); -} - -// Test link counts with a regular file as the child. -TEST_F(StatTest, LinkCountsWithRegularFileChild) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - struct stat st_parent_before = {}; - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_before), SyscallSucceeds()); - EXPECT_EQ(st_parent_before.st_nlink, 2); - - // Adding a regular file doesn't adjust the parent's link count. - const TempPath child = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - struct stat st_parent_after = {}; - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds()); - EXPECT_EQ(st_parent_after.st_nlink, 2); - - // The child should have a single link from the parent. - struct stat st_child = {}; - ASSERT_THAT(stat(child.path().c_str(), &st_child), SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(st_child.st_mode)); - EXPECT_EQ(st_child.st_nlink, 1); - - // Finally unlinking the child should not affect the parent's link count. - ASSERT_THAT(unlink(child.path().c_str()), SyscallSucceeds()); - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds()); - EXPECT_EQ(st_parent_after.st_nlink, 2); -} - -// This test verifies that inodes remain around when there is an open fd -// after link count hits 0. -// -// It is marked NoSave because we don't support saving unlinked files. -TEST_F(StatTest, ZeroLinksOpenFdRegularFileChild_NoSave) { - // Setting the enviornment variable GVISOR_GOFER_UNCACHED to any value - // will prevent this test from running, see the tmpfs lifecycle. - // - // We need to support this because when a file is unlinked and we forward - // the stat to the gofer it would return ENOENT. - const char* uncached_gofer = getenv("GVISOR_GOFER_UNCACHED"); - SKIP_IF(uncached_gofer != nullptr); - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - dir.path(), "hello", TempPath::kDefaultFileMode)); - - // The child should have a single link from the parent. - struct stat st_child_before = {}; - ASSERT_THAT(stat(child.path().c_str(), &st_child_before), SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(st_child_before.st_mode)); - EXPECT_EQ(st_child_before.st_nlink, 1); - EXPECT_EQ(st_child_before.st_size, 5); // Hello is 5 bytes. - - // Open the file so we can fstat after unlinking. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(child.path(), O_RDONLY)); - - // Now a stat should return ENOENT but we should still be able to stat - // via the open fd and fstat. - ASSERT_THAT(unlink(child.path().c_str()), SyscallSucceeds()); - - // Since the file has no more links stat should fail. - struct stat st_child_after = {}; - ASSERT_THAT(stat(child.path().c_str(), &st_child_after), - SyscallFailsWithErrno(ENOENT)); - - // Fstat should still allow us to access the same file via the fd. - struct stat st_child_fd = {}; - ASSERT_THAT(fstat(fd.get(), &st_child_fd), SyscallSucceeds()); - EXPECT_EQ(st_child_before.st_dev, st_child_fd.st_dev); - EXPECT_EQ(st_child_before.st_ino, st_child_fd.st_ino); - EXPECT_EQ(st_child_before.st_mode, st_child_fd.st_mode); - EXPECT_EQ(st_child_before.st_uid, st_child_fd.st_uid); - EXPECT_EQ(st_child_before.st_gid, st_child_fd.st_gid); - EXPECT_EQ(st_child_before.st_size, st_child_fd.st_size); - - // TODO(b/34861058): This isn't ideal but since fstatfs(2) will always return - // OVERLAYFS_SUPER_MAGIC we have no way to know if this fs is backed by a - // gofer which doesn't support links. - EXPECT_TRUE(st_child_fd.st_nlink == 0 || st_child_fd.st_nlink == 1); -} - -// Test link counts with a directory as the child. -TEST_F(StatTest, LinkCountsWithDirChild) { - // Skip this test if we are testing overlayfs because overlayfs does not - // (intentionally) return the correct nlink value for directories. - // See fs/overlayfs/inode.c:ovl_getattr(). - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))); - - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Before a child is added the two links are "." and the link from the parent. - struct stat st_parent_before = {}; - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_before), SyscallSucceeds()); - EXPECT_EQ(st_parent_before.st_nlink, 2); - - // Create a subdirectory and stat for the parent link counts. - const TempPath sub_dir = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - - // The three links are ".", the link from the parent, and the link from - // the child as "..". - struct stat st_parent_after = {}; - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds()); - EXPECT_EQ(st_parent_after.st_nlink, 3); - - // The child will have 1 link from the parent and 1 link which represents ".". - struct stat st_child = {}; - ASSERT_THAT(stat(sub_dir.path().c_str(), &st_child), SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st_child.st_mode)); - EXPECT_EQ(st_child.st_nlink, 2); - - // Finally delete the child dir and the parent link count should return to 2. - ASSERT_THAT(rmdir(sub_dir.path().c_str()), SyscallSucceeds()); - ASSERT_THAT(stat(dir.path().c_str(), &st_parent_after), SyscallSucceeds()); - - // Now we should only have links from the parent and "." since the subdir - // has been removed. - EXPECT_EQ(st_parent_after.st_nlink, 2); -} - -// Test statting a child of a non-directory. -TEST_F(StatTest, ChildOfNonDir) { - // Create a path that has a child of a regular file. - const std::string filename = JoinPath(test_file_name_, "child"); - - // Statting the path should return ENOTDIR. - struct stat st; - EXPECT_THAT(lstat(filename.c_str(), &st), SyscallFailsWithErrno(ENOTDIR)); -} - -// Test lstating a symlink directory. -TEST_F(StatTest, LstatSymlinkDir) { - // Create a directory and symlink to it. - const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string symlink_to_dir = NewTempAbsPath(); - EXPECT_THAT(symlink(dir.path().c_str(), symlink_to_dir.c_str()), - SyscallSucceeds()); - auto cleanup = Cleanup([&symlink_to_dir]() { - EXPECT_THAT(unlink(symlink_to_dir.c_str()), SyscallSucceeds()); - }); - - // Lstat on the symlink should return symlink data. - struct stat st = {}; - ASSERT_THAT(lstat(symlink_to_dir.c_str(), &st), SyscallSucceeds()); - EXPECT_FALSE(S_ISDIR(st.st_mode)); - EXPECT_TRUE(S_ISLNK(st.st_mode)); - - // Lstat on the symlink with a trailing slash should return the directory - // data. - ASSERT_THAT(lstat(absl::StrCat(symlink_to_dir, "/").c_str(), &st), - SyscallSucceeds()); - EXPECT_TRUE(S_ISDIR(st.st_mode)); - EXPECT_FALSE(S_ISLNK(st.st_mode)); -} - -// Verify that we get an ELOOP from too many symbolic links even when there -// are directories in the middle. -TEST_F(StatTest, LstatELOOPPath) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - std::string subdir_base = "subdir"; - ASSERT_THAT(mkdir(JoinPath(dir.path(), subdir_base).c_str(), 0755), - SyscallSucceeds()); - - std::string target = JoinPath(dir.path(), subdir_base, subdir_base); - std::string dst = JoinPath("..", subdir_base); - ASSERT_THAT(symlink(dst.c_str(), target.c_str()), SyscallSucceeds()); - auto cleanup = Cleanup( - [&target]() { EXPECT_THAT(unlink(target.c_str()), SyscallSucceeds()); }); - - // Now build a path which is /subdir/subdir/... repeated many times so that - // we can build a path that is shorter than PATH_MAX but can still cause - // too many symbolic links. Note: Every other subdir is actually a directory - // so we're not in a situation where it's a -> b -> a -> b, where a and b - // are symbolic links. - std::string path = dir.path(); - std::string subdir_append = absl::StrCat("/", subdir_base); - do { - absl::StrAppend(&path, subdir_append); - // Keep appending /subdir until we would overflow PATH_MAX. - } while ((path.size() + subdir_append.size()) < PATH_MAX); - - struct stat s = {}; - ASSERT_THAT(lstat(path.c_str(), &s), SyscallFailsWithErrno(ELOOP)); -} - -TEST(SimpleStatTest, DifferentFilesHaveDifferentDeviceInodeNumberPairs) { - // TODO(gvisor.dev/issue/1624): This test case fails in VFS1 save/restore - // tests because VFS1 gofer inode number assignment restarts after - // save/restore, such that the inodes for file1 and file2 (which are - // unreferenced and therefore not retained in sentry checkpoints before the - // calls to lstat()) are assigned the same inode number. - SKIP_IF(IsRunningWithVFS1() && IsRunningWithSaveRestore()); - - TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - MaybeSave(); - struct stat st1 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file1.path())); - MaybeSave(); - struct stat st2 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file2.path())); - EXPECT_FALSE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) - << "both files have device number " << st1.st_dev << " and inode number " - << st1.st_ino; -} - -// Ensure that inode allocation for anonymous devices work correctly across -// save/restore. In particular, inode numbers should be unique across S/R. -TEST(SimpleStatTest, AnonDeviceAllocatesUniqueInodesAcrossSaveRestore) { - // Use sockets as a convenient way to create inodes on an anonymous device. - int fd; - ASSERT_THAT(fd = socket(AF_UNIX, SOCK_STREAM, 0), SyscallSucceeds()); - FileDescriptor fd1(fd); - MaybeSave(); - ASSERT_THAT(fd = socket(AF_UNIX, SOCK_STREAM, 0), SyscallSucceeds()); - FileDescriptor fd2(fd); - - struct stat st1; - struct stat st2; - ASSERT_THAT(fstat(fd1.get(), &st1), SyscallSucceeds()); - ASSERT_THAT(fstat(fd2.get(), &st2), SyscallSucceeds()); - - // The two fds should have different inode numbers. - EXPECT_NE(st2.st_ino, st1.st_ino); - - // Verify again after another S/R cycle. The inode numbers should remain the - // same. - MaybeSave(); - - struct stat st1_after; - struct stat st2_after; - ASSERT_THAT(fstat(fd1.get(), &st1_after), SyscallSucceeds()); - ASSERT_THAT(fstat(fd2.get(), &st2_after), SyscallSucceeds()); - - EXPECT_EQ(st1_after.st_ino, st1.st_ino); - EXPECT_EQ(st2_after.st_ino, st2.st_ino); -} - -#ifndef SYS_statx -#if defined(__x86_64__) -#define SYS_statx 332 -#elif defined(__aarch64__) -#define SYS_statx 291 -#else -#error "Unknown architecture" -#endif -#endif // SYS_statx - -#ifndef STATX_ALL -#define STATX_ALL 0x00000fffU -#endif // STATX_ALL - -// struct kernel_statx_timestamp is a Linux statx_timestamp struct. -struct kernel_statx_timestamp { - int64_t tv_sec; - uint32_t tv_nsec; - int32_t __reserved; -}; - -// struct kernel_statx is a Linux statx struct. Old versions of glibc do not -// expose it. See include/uapi/linux/stat.h -struct kernel_statx { - uint32_t stx_mask; - uint32_t stx_blksize; - uint64_t stx_attributes; - uint32_t stx_nlink; - uint32_t stx_uid; - uint32_t stx_gid; - uint16_t stx_mode; - uint16_t __spare0[1]; - uint64_t stx_ino; - uint64_t stx_size; - uint64_t stx_blocks; - uint64_t stx_attributes_mask; - struct kernel_statx_timestamp stx_atime; - struct kernel_statx_timestamp stx_btime; - struct kernel_statx_timestamp stx_ctime; - struct kernel_statx_timestamp stx_mtime; - uint32_t stx_rdev_major; - uint32_t stx_rdev_minor; - uint32_t stx_dev_major; - uint32_t stx_dev_minor; - uint64_t __spare2[14]; -}; - -int statx(int dirfd, const char* pathname, int flags, unsigned int mask, - struct kernel_statx* statxbuf) { - return syscall(SYS_statx, dirfd, pathname, flags, mask, statxbuf); -} - -TEST_F(StatTest, StatxAbsPath) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - struct kernel_statx stx; - EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxRelPathDirFD) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - struct kernel_statx stx; - auto const dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); - auto filename = std::string(Basename(test_file_name_)); - - EXPECT_THAT(statx(dirfd.get(), filename.c_str(), 0, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxRelPathCwd) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - auto filename = std::string(Basename(test_file_name_)); - struct kernel_statx stx; - EXPECT_THAT(statx(AT_FDCWD, filename.c_str(), 0, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxEmptyPath) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY)); - struct kernel_statx stx; - EXPECT_THAT(statx(fd.get(), "", AT_EMPTY_PATH, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxDoesNotRejectExtraneousMaskBits) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - struct kernel_statx stx; - // Set all mask bits except for STATX__RESERVED. - uint mask = 0xffffffff & ~0x80000000; - EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, mask, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxRejectsReservedMaskBit) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - struct kernel_statx stx; - // Set STATX__RESERVED in the mask. - EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, 0x80000000, &stx), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(StatTest, StatxSymlink) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - std::string parent_dir = "/tmp"; - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(parent_dir, test_file_name_)); - std::string p = link.path(); - - struct kernel_statx stx; - EXPECT_THAT(statx(AT_FDCWD, p.c_str(), AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISLNK(stx.stx_mode)); - EXPECT_THAT(statx(AT_FDCWD, p.c_str(), 0, STATX_ALL, &stx), - SyscallSucceeds()); - EXPECT_TRUE(S_ISREG(stx.stx_mode)); -} - -TEST_F(StatTest, StatxInvalidFlags) { - SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 && - errno == ENOSYS); - - struct kernel_statx stx; - EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(), 12345, 0, &stx), - SyscallFailsWithErrno(EINVAL)); - - // Sync flags are mutually exclusive. - EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(), - AT_STATX_FORCE_SYNC | AT_STATX_DONT_SYNC, 0, &stx), - SyscallFailsWithErrno(EINVAL)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/stat_times.cc b/test/syscalls/linux/stat_times.cc deleted file mode 100644 index 68c0bef09..000000000 --- a/test/syscalls/linux/stat_times.cc +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> - -#include <tuple> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::testing::IsEmpty; -using ::testing::Not; - -std::tuple<absl::Time, absl::Time, absl::Time> GetTime(const TempPath& file) { - struct stat statbuf = {}; - EXPECT_THAT(stat(file.path().c_str(), &statbuf), SyscallSucceeds()); - - const auto atime = absl::TimeFromTimespec(statbuf.st_atim); - const auto mtime = absl::TimeFromTimespec(statbuf.st_mtim); - const auto ctime = absl::TimeFromTimespec(statbuf.st_ctim); - return std::make_tuple(atime, mtime, ctime); -} - -enum class AtimeEffect { - Unchanged, - Changed, -}; - -enum class MtimeEffect { - Unchanged, - Changed, -}; - -enum class CtimeEffect { - Unchanged, - Changed, -}; - -// Tests that fn modifies the atime/mtime/ctime of path as specified. -void CheckTimes(const TempPath& path, std::function<void()> fn, - AtimeEffect atime_effect, MtimeEffect mtime_effect, - CtimeEffect ctime_effect) { - absl::Time atime, mtime, ctime; - std::tie(atime, mtime, ctime) = GetTime(path); - - // FIXME(b/132819225): gVisor filesystem timestamps inconsistently use the - // internal or host clock, which may diverge slightly. Allow some slack on - // times to account for the difference. - // - // Here we sleep for 1s so that initial creation of path doesn't fall within - // the before slack window. - absl::SleepFor(absl::Seconds(1)); - - const absl::Time before = absl::Now() - absl::Seconds(1); - - // Perform the op. - fn(); - - const absl::Time after = absl::Now() + absl::Seconds(1); - - absl::Time atime2, mtime2, ctime2; - std::tie(atime2, mtime2, ctime2) = GetTime(path); - - if (atime_effect == AtimeEffect::Changed) { - EXPECT_LE(before, atime2); - EXPECT_GE(after, atime2); - EXPECT_GT(atime2, atime); - } else { - EXPECT_EQ(atime2, atime); - } - - if (mtime_effect == MtimeEffect::Changed) { - EXPECT_LE(before, mtime2); - EXPECT_GE(after, mtime2); - EXPECT_GT(mtime2, mtime); - } else { - EXPECT_EQ(mtime2, mtime); - } - - if (ctime_effect == CtimeEffect::Changed) { - EXPECT_LE(before, ctime2); - EXPECT_GE(after, ctime2); - EXPECT_GT(ctime2, ctime); - } else { - EXPECT_EQ(ctime2, ctime); - } -} - -// File creation time is reflected in atime, mtime, and ctime. -TEST(StatTimesTest, FileCreation) { - const DisableSave ds; // Timing-related test. - - // Get a time for when the file is created. - // - // FIXME(b/132819225): See above. - const absl::Time before = absl::Now() - absl::Seconds(1); - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const absl::Time after = absl::Now() + absl::Seconds(1); - - absl::Time atime, mtime, ctime; - std::tie(atime, mtime, ctime) = GetTime(file); - - EXPECT_LE(before, atime); - EXPECT_LE(before, mtime); - EXPECT_LE(before, ctime); - EXPECT_GE(after, atime); - EXPECT_GE(after, mtime); - EXPECT_GE(after, ctime); -} - -// Calling chmod on a file changes ctime. -TEST(StatTimesTest, FileChmod) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - auto fn = [&] { - EXPECT_THAT(chmod(file.path().c_str(), 0666), SyscallSucceeds()); - }; - CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged, - CtimeEffect::Changed); -} - -// Renaming a file changes ctime. -TEST(StatTimesTest, FileRename) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - const std::string newpath = NewTempAbsPath(); - - auto fn = [&] { - ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()), - SyscallSucceeds()); - file.reset(newpath); - }; - CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged, - CtimeEffect::Changed); -} - -// Renaming a file changes ctime, even with an open FD. -// -// NOTE(b/132732387): This is a regression test for fs/gofer failing to update -// cached ctime. -TEST(StatTimesTest, FileRenameOpenFD) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // Holding an FD shouldn't affect behavior. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); - - const std::string newpath = NewTempAbsPath(); - - // FIXME(b/132814682): Restore fails with an uncached gofer and an open FD - // across rename. - // - // N.B. The logic here looks backwards because it isn't possible to - // conditionally disable save, only conditionally re-enable it. - DisableSave ds; - if (!getenv("GVISOR_GOFER_UNCACHED")) { - ds.reset(); - } - - auto fn = [&] { - ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()), - SyscallSucceeds()); - file.reset(newpath); - }; - CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged, - CtimeEffect::Changed); -} - -// Calling utimes on a file changes ctime and the time that we ask to change -// (atime to now in this case). -TEST(StatTimesTest, FileUtimes) { - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - auto fn = [&] { - const struct timespec ts[2] = {{0, UTIME_NOW}, {0, UTIME_OMIT}}; - ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), ts, 0), - SyscallSucceeds()); - }; - CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged, - CtimeEffect::Changed); -} - -// Truncating a file changes mtime and ctime. -TEST(StatTimesTest, FileTruncate) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666)); - - auto fn = [&] { - EXPECT_THAT(truncate(file.path().c_str(), 0), SyscallSucceeds()); - }; - CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed, - CtimeEffect::Changed); -} - -// Writing a file changes mtime and ctime. -TEST(StatTimesTest, FileWrite) { - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0)); - - auto fn = [&] { - const std::string contents = "all the single dollars"; - EXPECT_THAT(WriteFd(fd.get(), contents.data(), contents.size()), - SyscallSucceeds()); - }; - CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed, - CtimeEffect::Changed); -} - -// Reading a file changes atime. -TEST(StatTimesTest, FileRead) { - const std::string contents = "bills bills bills"; - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), contents, 0666)); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY, 0)); - - auto fn = [&] { - char buf[20]; - ASSERT_THAT(ReadFd(fd.get(), buf, sizeof(buf)), - SyscallSucceedsWithValue(contents.size())); - }; - CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged, - CtimeEffect::Unchanged); -} - -// Listing files in a directory changes atime. -TEST(StatTimesTest, DirList) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - auto fn = [&] { - const auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false)); - EXPECT_THAT(contents, Not(IsEmpty())); - }; - CheckTimes(dir, fn, AtimeEffect::Changed, MtimeEffect::Unchanged, - CtimeEffect::Unchanged); -} - -// Creating a file in a directory changes mtime and ctime. -TEST(StatTimesTest, DirCreateFile) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - TempPath file; - auto fn = [&] { - file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - }; - CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed, - CtimeEffect::Changed); -} - -// Creating a directory in a directory changes mtime and ctime. -TEST(StatTimesTest, DirCreateDir) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - TempPath dir2; - auto fn = [&] { - dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - }; - CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed, - CtimeEffect::Changed); -} - -// Removing a file from a directory changes mtime and ctime. -TEST(StatTimesTest, DirRemoveFile) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - auto fn = [&] { file.reset(); }; - CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed, - CtimeEffect::Changed); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc deleted file mode 100644 index d4ea8e026..000000000 --- a/test/syscalls/linux/statfs.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <linux/magic.h> -#include <sys/statfs.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(StatfsTest, CannotStatBadPath) { - auto temp_file = NewTempAbsPathInDir("/tmp"); - - struct statfs st; - EXPECT_THAT(statfs(temp_file.c_str(), &st), SyscallFailsWithErrno(ENOENT)); -} - -TEST(StatfsTest, InternalTmpfs) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - struct statfs st; - EXPECT_THAT(statfs(temp_file.path().c_str(), &st), SyscallSucceeds()); -} - -TEST(StatfsTest, InternalDevShm) { - struct statfs st; - EXPECT_THAT(statfs("/dev/shm", &st), SyscallSucceeds()); - - // This assumes that /dev/shm is tmpfs. - // Note: We could be an overlay on some configurations. - EXPECT_TRUE(st.f_type == TMPFS_MAGIC || st.f_type == OVERLAYFS_SUPER_MAGIC); -} - -TEST(FstatfsTest, CannotStatBadFd) { - struct statfs st; - EXPECT_THAT(fstatfs(-1, &st), SyscallFailsWithErrno(EBADF)); -} - -TEST(FstatfsTest, InternalTmpfs) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); - - struct statfs st; - EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); -} - -TEST(FstatfsTest, CanStatFileWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); - - struct statfs st; - EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); -} - -TEST(FstatfsTest, InternalDevShm) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/shm", O_RDONLY)); - - struct statfs st; - EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sticky.cc b/test/syscalls/linux/sticky.cc deleted file mode 100644 index 4afed6d08..000000000 --- a/test/syscalls/linux/sticky.cc +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <grp.h> -#include <sys/prctl.h> -#include <sys/types.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -ABSL_FLAG(int32_t, scratch_uid, 65534, "first scratch UID"); -ABSL_FLAG(int32_t, scratch_gid, 65534, "first scratch GID"); - -namespace gvisor { -namespace testing { - -namespace { - -TEST(StickyTest, StickyBitPermDenied) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); - - // After changing credentials below, we need to use an open fd to make - // modifications in the parent dir, because there is no guarantee that we will - // still have the ability to open it. - const FileDescriptor parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); - ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); - ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); - ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); - - // Drop privileges and change IDs only in child thread, or else this parent - // thread won't be able to open some log files after the test ends. - ScopedThread([&] { - // Drop privileges. - if (HaveCapability(CAP_FOWNER).ValueOrDie()) { - EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false)); - } - - // Change EUID and EGID. - EXPECT_THAT( - syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), - SyscallSucceeds()); - EXPECT_THAT( - syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1), - SyscallSucceeds()); - - EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(unlinkat(parent_fd.get(), "file", 0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), - SyscallFailsWithErrno(EPERM)); - }); -} - -TEST(StickyTest, StickyBitSameUID) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); - - // After changing credentials below, we need to use an open fd to make - // modifications in the parent dir, because there is no guarantee that we will - // still have the ability to open it. - const FileDescriptor parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); - ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); - ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); - ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); - - // Drop privileges and change IDs only in child thread, or else this parent - // thread won't be able to open some log files after the test ends. - ScopedThread([&] { - // Drop privileges. - if (HaveCapability(CAP_FOWNER).ValueOrDie()) { - EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, false)); - } - - // Change EGID. - EXPECT_THAT( - syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), - SyscallSucceeds()); - - // We still have the same EUID. - EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), - SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), - SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds()); - }); -} - -TEST(StickyTest, StickyBitCapFOWNER) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); - - const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds()); - - // After changing credentials below, we need to use an open fd to make - // modifications in the parent dir, because there is no guarantee that we will - // still have the ability to open it. - const FileDescriptor parent_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY)); - ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds()); - ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds()); - ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds()); - - // Drop privileges and change IDs only in child thread, or else this parent - // thread won't be able to open some log files after the test ends. - ScopedThread([&] { - // Set PR_SET_KEEPCAPS. - EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); - - // Change EUID and EGID. - EXPECT_THAT( - syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1), - SyscallSucceeds()); - EXPECT_THAT( - syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1), - SyscallSucceeds()); - - EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, true)); - EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"), - SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR), - SyscallSucceeds()); - EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds()); - }); -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc deleted file mode 100644 index ea219a091..000000000 --- a/test/syscalls/linux/symlink.cc +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <string.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -mode_t FilePermission(const std::string& path) { - struct stat buf = {0}; - TEST_CHECK(lstat(path.c_str(), &buf) == 0); - return buf.st_mode & 0777; -} - -// Test that name collisions are checked on the new link path, not the source -// path. Regression test for b/31782115. -TEST(SymlinkTest, CanCreateSymlinkWithCachedSourceDirent) { - const std::string srcname = NewTempAbsPath(); - const std::string newname = NewTempAbsPath(); - const std::string basedir = std::string(Dirname(srcname)); - ASSERT_EQ(basedir, Dirname(newname)); - - ASSERT_THAT(chdir(basedir.c_str()), SyscallSucceeds()); - - // Open the source node to cause the underlying dirent to be cached. It will - // remain cached while we have the file open. - int fd; - ASSERT_THAT(fd = open(srcname.c_str(), O_CREAT | O_RDWR, 0666), - SyscallSucceeds()); - FileDescriptor fd_closer(fd); - - // Attempt to create a symlink. If the bug exists, this will fail since the - // dirent link creation code will check for a name collision on the source - // link name. - EXPECT_THAT(symlink(std::string(Basename(srcname)).c_str(), - std::string(Basename(newname)).c_str()), - SyscallSucceeds()); -} - -TEST(SymlinkTest, CanCreateSymlinkFile) { - const std::string oldname = NewTempAbsPath(); - const std::string newname = NewTempAbsPath(); - - int fd; - ASSERT_THAT(fd = open(oldname.c_str(), O_CREAT | O_RDWR, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT(symlink(oldname.c_str(), newname.c_str()), SyscallSucceeds()); - EXPECT_EQ(FilePermission(newname), 0777); - - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink(newname)); - EXPECT_EQ(oldname, link); - - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, CanCreateSymlinkDir) { - const std::string olddir = NewTempAbsPath(); - const std::string newdir = NewTempAbsPath(); - - EXPECT_THAT(mkdir(olddir.c_str(), 0777), SyscallSucceeds()); - EXPECT_THAT(symlink(olddir.c_str(), newdir.c_str()), SyscallSucceeds()); - EXPECT_EQ(FilePermission(newdir), 0777); - - auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink(newdir)); - EXPECT_EQ(olddir, link); - - EXPECT_THAT(unlink(newdir.c_str()), SyscallSucceeds()); - - ASSERT_THAT(rmdir(olddir.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, CannotCreateSymlinkInReadOnlyDir) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - const std::string olddir = NewTempAbsPath(); - ASSERT_THAT(mkdir(olddir.c_str(), 0444), SyscallSucceeds()); - - const std::string newdir = NewTempAbsPathInDir(olddir); - EXPECT_THAT(symlink(olddir.c_str(), newdir.c_str()), - SyscallFailsWithErrno(EACCES)); - - ASSERT_THAT(rmdir(olddir.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, CannotSymlinkOverExistingFile) { - const auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto newfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - EXPECT_THAT(symlink(oldfile.path().c_str(), newfile.path().c_str()), - SyscallFailsWithErrno(EEXIST)); -} - -TEST(SymlinkTest, CannotSymlinkOverExistingDir) { - const auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto newdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - EXPECT_THAT(symlink(oldfile.path().c_str(), newdir.path().c_str()), - SyscallFailsWithErrno(EEXIST)); -} - -TEST(SymlinkTest, OldnameIsEmpty) { - const std::string newname = NewTempAbsPath(); - EXPECT_THAT(symlink("", newname.c_str()), SyscallFailsWithErrno(ENOENT)); -} - -TEST(SymlinkTest, OldnameIsDangling) { - const std::string newname = NewTempAbsPath(); - EXPECT_THAT(symlink("/dangling", newname.c_str()), SyscallSucceeds()); - - // This is required for S/R random save tests, which pre-run this test - // in the same TEST_TMPDIR, which means that we need to clean it for any - // operations exclusively creating files, like symlink above. - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, NewnameCannotExist) { - const std::string newname = - JoinPath(GetAbsoluteTestTmpdir(), "thisdoesnotexist", "foo"); - EXPECT_THAT(symlink("/thisdoesnotmatter", newname.c_str()), - SyscallFailsWithErrno(ENOENT)); -} - -TEST(SymlinkTest, CanEvaluateLink) { - const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - - // We are going to assert that the symlink inode id is the same as the linked - // file's inode id. In order for the inode id to be stable across - // save/restore, it must be kept open. The FileDescriptor type will do that - // for us automatically. - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); - struct stat file_st; - EXPECT_THAT(fstat(fd.get(), &file_st), SyscallSucceeds()); - - const std::string link = NewTempAbsPath(); - EXPECT_THAT(symlink(file.path().c_str(), link.c_str()), SyscallSucceeds()); - EXPECT_EQ(FilePermission(link), 0777); - - auto linkfd = ASSERT_NO_ERRNO_AND_VALUE(Open(link.c_str(), O_RDWR)); - struct stat link_st; - EXPECT_THAT(fstat(linkfd.get(), &link_st), SyscallSucceeds()); - - // Check that in fact newname points to the file we expect. - EXPECT_EQ(file_st.st_dev, link_st.st_dev); - EXPECT_EQ(file_st.st_ino, link_st.st_ino); -} - -TEST(SymlinkTest, TargetIsNotMapped) { - const std::string oldname = NewTempAbsPath(); - const std::string newname = NewTempAbsPath(); - - int fd; - // Create the target so that when we read the link, it exists. - ASSERT_THAT(fd = open(oldname.c_str(), O_CREAT | O_RDWR, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - // Create a symlink called newname that points to oldname. - EXPECT_THAT(symlink(oldname.c_str(), newname.c_str()), SyscallSucceeds()); - - std::vector<char> buf(1024); - int linksize; - // Read the link and assert that the oldname is still the same. - EXPECT_THAT(linksize = readlink(newname.c_str(), buf.data(), 1024), - SyscallSucceeds()); - EXPECT_EQ(0, strncmp(oldname.c_str(), buf.data(), linksize)); - - EXPECT_THAT(unlink(newname.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(oldname.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, PreadFromSymlink) { - std::string name = NewTempAbsPath(); - int fd; - ASSERT_THAT(fd = open(name.c_str(), O_CREAT, 0644), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - - std::string linkname = NewTempAbsPath(); - ASSERT_THAT(symlink(name.c_str(), linkname.c_str()), SyscallSucceeds()); - - ASSERT_THAT(fd = open(linkname.c_str(), O_RDONLY), SyscallSucceeds()); - - char buf[1024]; - EXPECT_THAT(pread64(fd, buf, 1024, 0), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT(unlink(name.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(linkname.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, PwriteToSymlink) { - std::string name = NewTempAbsPath(); - int fd; - ASSERT_THAT(fd = open(name.c_str(), O_CREAT, 0644), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - - std::string linkname = NewTempAbsPath(); - ASSERT_THAT(symlink(name.c_str(), linkname.c_str()), SyscallSucceeds()); - - ASSERT_THAT(fd = open(linkname.c_str(), O_WRONLY), SyscallSucceeds()); - - const int data_size = 10; - const std::string data = std::string(data_size, 'a'); - EXPECT_THAT(pwrite64(fd, data.c_str(), data.size(), 0), - SyscallSucceedsWithValue(data.size())); - - ASSERT_THAT(close(fd), SyscallSucceeds()); - ASSERT_THAT(fd = open(name.c_str(), O_RDONLY), SyscallSucceeds()); - - char buf[data_size + 1]; - EXPECT_THAT(pread64(fd, buf, data.size(), 0), SyscallSucceeds()); - buf[data.size()] = '\0'; - EXPECT_STREQ(buf, data.c_str()); - - ASSERT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT(unlink(name.c_str()), SyscallSucceeds()); - EXPECT_THAT(unlink(linkname.c_str()), SyscallSucceeds()); -} - -TEST(SymlinkTest, SymlinkAtDegradedPermissions_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - int dirfd; - ASSERT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0), - SyscallSucceeds()); - - const DisableSave ds; // Permissions are dropped. - EXPECT_THAT(fchmod(dirfd, 0), SyscallSucceeds()); - - std::string basename = std::string(Basename(file.path())); - EXPECT_THAT(symlinkat("/dangling", dirfd, basename.c_str()), - SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(close(dirfd), SyscallSucceeds()); -} - -TEST(SymlinkTest, SymlinkAtDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string filepath = NewTempAbsPathInDir(dir.path()); - const std::string base = std::string(Basename(filepath)); - FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); - - EXPECT_THAT(symlinkat("/dangling", dirfd.get(), base.c_str()), - SyscallSucceeds()); -} - -TEST(SymlinkTest, ReadlinkAtDirWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string filepath = NewTempAbsPathInDir(dir.path()); - const std::string base = std::string(Basename(filepath)); - ASSERT_THAT(symlink("/dangling", filepath.c_str()), SyscallSucceeds()); - - FileDescriptor dirfd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); - - std::vector<char> buf(1024); - int linksize; - EXPECT_THAT( - linksize = readlinkat(dirfd.get(), base.c_str(), buf.data(), 1024), - SyscallSucceeds()); - EXPECT_EQ(0, strncmp("/dangling", buf.data(), linksize)); -} - -TEST(SymlinkTest, ReadlinkAtDegradedPermissions_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string oldpath = NewTempAbsPathInDir(dir.path()); - const std::string oldbase = std::string(Basename(oldpath)); - ASSERT_THAT(symlink("/dangling", oldpath.c_str()), SyscallSucceeds()); - - int dirfd; - EXPECT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0), - SyscallSucceeds()); - - const DisableSave ds; // Permissions are dropped. - EXPECT_THAT(fchmod(dirfd, 0), SyscallSucceeds()); - - char buf[1024]; - int linksize; - EXPECT_THAT(linksize = readlinkat(dirfd, oldbase.c_str(), buf, 1024), - SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(close(dirfd), SyscallSucceeds()); -} - -TEST(SymlinkTest, ChmodSymlink) { - auto target = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string newpath = NewTempAbsPath(); - ASSERT_THAT(symlink(target.path().c_str(), newpath.c_str()), - SyscallSucceeds()); - EXPECT_EQ(FilePermission(newpath), 0777); - EXPECT_THAT(chmod(newpath.c_str(), 0666), SyscallSucceeds()); - EXPECT_EQ(FilePermission(newpath), 0777); -} - -// Test that following a symlink updates the atime on the symlink. -TEST(SymlinkTest, FollowUpdatesATime) { - const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string link = NewTempAbsPath(); - EXPECT_THAT(symlink(file.path().c_str(), link.c_str()), SyscallSucceeds()); - - // Lstat the symlink. - struct stat st_before_follow; - ASSERT_THAT(lstat(link.c_str(), &st_before_follow), SyscallSucceeds()); - - // Let the clock advance. - absl::SleepFor(absl::Seconds(1)); - - // Open the file via the symlink. - int fd; - ASSERT_THAT(fd = open(link.c_str(), O_RDWR, 0666), SyscallSucceeds()); - FileDescriptor fd_closer(fd); - - // Lstat the symlink again, and check that atime is updated. - struct stat st_after_follow; - ASSERT_THAT(lstat(link.c_str(), &st_after_follow), SyscallSucceeds()); - EXPECT_LT(st_before_follow.st_atime, st_after_follow.st_atime); -} - -TEST(SymlinkTest, SymlinkAtEmptyPath) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - auto fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY, 0666)); - EXPECT_THAT(symlinkat(file.path().c_str(), fd.get(), ""), - SyscallFailsWithErrno(ENOENT)); -} - -class ParamSymlinkTest : public ::testing::TestWithParam<std::string> {}; - -// Test that creating an existing symlink with creat will create the target. -TEST_P(ParamSymlinkTest, CreatLinkCreatesTarget) { - const std::string target = GetParam(); - const std::string linkpath = NewTempAbsPath(); - - ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds()); - - int fd; - EXPECT_THAT(fd = creat(linkpath.c_str(), 0666), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - struct stat st; - EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds()); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); - ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds()); -} - -// Test that opening an existing symlink with O_CREAT will create the target. -TEST_P(ParamSymlinkTest, OpenLinkCreatesTarget) { - const std::string target = GetParam(); - const std::string linkpath = NewTempAbsPath(); - - ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds()); - - int fd; - EXPECT_THAT(fd = open(linkpath.c_str(), O_CREAT, 0666), SyscallSucceeds()); - ASSERT_THAT(close(fd), SyscallSucceeds()); - - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - struct stat st; - EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds()); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); - ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds()); -} - -// Test that opening a self-symlink with O_CREAT will fail with ELOOP. -TEST_P(ParamSymlinkTest, CreateExistingSelfLink) { - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - - const std::string linkpath = GetParam(); - ASSERT_THAT(symlink(linkpath.c_str(), linkpath.c_str()), SyscallSucceeds()); - - EXPECT_THAT(open(linkpath.c_str(), O_CREAT, 0666), - SyscallFailsWithErrno(ELOOP)); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); -} - -// Test that opening a file that is a symlink to its parent directory fails -// with ELOOP. -TEST_P(ParamSymlinkTest, CreateExistingParentLink) { - ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds()); - - const std::string linkpath = GetParam(); - const std::string target = JoinPath(linkpath, "child"); - ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds()); - - EXPECT_THAT(open(linkpath.c_str(), O_CREAT, 0666), - SyscallFailsWithErrno(ELOOP)); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); -} - -// Test that opening an existing symlink with O_CREAT|O_EXCL will fail with -// EEXIST. -TEST_P(ParamSymlinkTest, OpenLinkExclFails) { - const std::string target = GetParam(); - const std::string linkpath = NewTempAbsPath(); - - ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds()); - - EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_EXCL, 0666), - SyscallFailsWithErrno(EEXIST)); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); -} - -// Test that opening an existing symlink with O_CREAT|O_NOFOLLOW will fail with -// ELOOP. -TEST_P(ParamSymlinkTest, OpenLinkNoFollowFails) { - const std::string target = GetParam(); - const std::string linkpath = NewTempAbsPath(); - - ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds()); - - EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_NOFOLLOW, 0666), - SyscallFailsWithErrno(ELOOP)); - - ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds()); -} - -INSTANTIATE_TEST_SUITE_P(AbsAndRelTarget, ParamSymlinkTest, - ::testing::Values(NewTempAbsPath(), NewTempRelPath())); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sync.cc b/test/syscalls/linux/sync.cc deleted file mode 100644 index 84a2c4ed7..000000000 --- a/test/syscalls/linux/sync.cc +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <stdio.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SyncTest, SyncEverything) { - ASSERT_THAT(syscall(SYS_sync), SyscallSucceeds()); -} - -TEST(SyncTest, SyncFileSytem) { - int fd; - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - ASSERT_THAT(fd = open(f.path().c_str(), O_RDONLY), SyscallSucceeds()); - EXPECT_THAT(syncfs(fd), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(SyncTest, SyncFromPipe) { - int pipes[2]; - EXPECT_THAT(pipe(pipes), SyscallSucceeds()); - EXPECT_THAT(syncfs(pipes[0]), SyscallSucceeds()); - EXPECT_THAT(syncfs(pipes[1]), SyscallSucceeds()); - EXPECT_THAT(close(pipes[0]), SyscallSucceeds()); - EXPECT_THAT(close(pipes[1]), SyscallSucceeds()); -} - -TEST(SyncTest, CannotSyncFileSystemAtBadFd) { - EXPECT_THAT(syncfs(-1), SyscallFailsWithErrno(EBADF)); -} - -TEST(SyncTest, CannotSyncFileSystemAtOpathFD) { - SKIP_IF(IsRunningWithVFS1()); - - const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); - - EXPECT_THAT(syncfs(fd.get()), SyscallFailsWithErrno(EBADF)); -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sync_file_range.cc b/test/syscalls/linux/sync_file_range.cc deleted file mode 100644 index 36cc42043..000000000 --- a/test/syscalls/linux/sync_file_range.cc +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <stdio.h> -#include <unistd.h> - -#include <string> - -#include "gtest/gtest.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SyncFileRangeTest, TempFileSucceeds) { - auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR)); - constexpr char data[] = "some data to sync"; - int fd = f.get(); - - EXPECT_THAT(write(fd, data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); - EXPECT_THAT(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE), - SyscallSucceeds()); - EXPECT_THAT(sync_file_range(fd, 0, 0, 0), SyscallSucceeds()); - EXPECT_THAT( - sync_file_range(fd, 0, 0, - SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER | - SYNC_FILE_RANGE_WAIT_BEFORE), - SyscallSucceeds()); - EXPECT_THAT(sync_file_range( - fd, 0, 1, SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER), - SyscallSucceeds()); - EXPECT_THAT(sync_file_range( - fd, 1, 0, SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER), - SyscallSucceeds()); -} - -TEST(SyncFileRangeTest, CannotSyncFileRangeOnUnopenedFd) { - auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR)); - constexpr char data[] = "some data to sync"; - int fd = f.get(); - - EXPECT_THAT(write(fd, data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); - - pid_t pid = fork(); - if (pid == 0) { - f.reset(); - - // fd is now invalid. - TEST_CHECK(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE) == -1); - TEST_PCHECK(errno == EBADF); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - - int status = 0; - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(WEXITSTATUS(status), 0); -} - -TEST(SyncFileRangeTest, BadArgs) { - auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR)); - int fd = f.get(); - - EXPECT_THAT(sync_file_range(fd, -1, 0, 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(sync_file_range(fd, 0, -1, 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(sync_file_range(fd, 8912, INT64_MAX - 4096, 0), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(SyncFileRangeTest, CannotSyncFileRangeWithWaitBefore) { - auto tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path(), O_RDWR)); - constexpr char data[] = "some data to sync"; - int fd = f.get(); - - EXPECT_THAT(write(fd, data, sizeof(data)), - SyscallSucceedsWithValue(sizeof(data))); - if (IsRunningOnGvisor()) { - EXPECT_THAT(sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WAIT_BEFORE), - SyscallFailsWithErrno(ENOSYS)); - EXPECT_THAT( - sync_file_range(fd, 0, 0, - SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE), - SyscallFailsWithErrno(ENOSYS)); - } -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sysinfo.cc b/test/syscalls/linux/sysinfo.cc deleted file mode 100644 index 1a71256da..000000000 --- a/test/syscalls/linux/sysinfo.cc +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This is a very simple sanity test to validate that the sysinfo syscall is -// supported by gvisor and returns sane values. -#include <sys/syscall.h> -#include <sys/sysinfo.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(SysinfoTest, SysinfoIsCallable) { - struct sysinfo ignored = {}; - EXPECT_THAT(syscall(SYS_sysinfo, &ignored), SyscallSucceedsWithValue(0)); -} - -TEST(SysinfoTest, EfaultProducedOnBadAddress) { - // Validate that we return EFAULT when a bad address is provided. - // specified by man 2 sysinfo - EXPECT_THAT(syscall(SYS_sysinfo, nullptr), SyscallFailsWithErrno(EFAULT)); -} - -TEST(SysinfoTest, TotalRamSaneValue) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - EXPECT_GT(s.totalram, 0); -} - -TEST(SysinfoTest, MemunitSet) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - EXPECT_GE(s.mem_unit, 1); -} - -TEST(SysinfoTest, UptimeSaneValue) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - EXPECT_GE(s.uptime, 0); -} - -TEST(SysinfoTest, UptimeIncreasingValue) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - absl::SleepFor(absl::Seconds(2)); - struct sysinfo s2 = {}; - EXPECT_THAT(sysinfo(&s2), SyscallSucceedsWithValue(0)); - EXPECT_LT(s.uptime, s2.uptime); -} - -TEST(SysinfoTest, FreeRamSaneValue) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - EXPECT_GT(s.freeram, 0); - EXPECT_LT(s.freeram, s.totalram); -} - -TEST(SysinfoTest, NumProcsSaneValue) { - struct sysinfo s = {}; - EXPECT_THAT(sysinfo(&s), SyscallSucceedsWithValue(0)); - EXPECT_GT(s.procs, 0); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/syslog.cc b/test/syscalls/linux/syslog.cc deleted file mode 100644 index 9a7407d96..000000000 --- a/test/syscalls/linux/syslog.cc +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/klog.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr int SYSLOG_ACTION_READ_ALL = 3; -constexpr int SYSLOG_ACTION_SIZE_BUFFER = 10; - -int Syslog(int type, char* buf, int len) { - return syscall(__NR_syslog, type, buf, len); -} - -// Only SYSLOG_ACTION_SIZE_BUFFER and SYSLOG_ACTION_READ_ALL are implemented in -// gVisor. - -TEST(Syslog, Size) { - EXPECT_THAT(Syslog(SYSLOG_ACTION_SIZE_BUFFER, nullptr, 0), SyscallSucceeds()); -} - -TEST(Syslog, ReadAll) { - // There might not be anything to read, so we can't check the write count. - char buf[100]; - EXPECT_THAT(Syslog(SYSLOG_ACTION_READ_ALL, buf, sizeof(buf)), - SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/sysret.cc b/test/syscalls/linux/sysret.cc deleted file mode 100644 index 19ffbd85b..000000000 --- a/test/syscalls/linux/sysret.cc +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Tests to verify that the behavior of linux and gvisor matches when -// 'sysret' returns to bad (aka non-canonical) %rip or %rsp. - -#include <linux/elf.h> -#include <sys/ptrace.h> -#include <sys/user.h> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr uint64_t kNonCanonicalRip = 0xCCCC000000000000; -constexpr uint64_t kNonCanonicalRsp = 0xFFFF000000000000; - -class SysretTest : public ::testing::Test { - protected: - struct user_regs_struct regs_; - struct iovec iov; - pid_t child_; - - void SetUp() override { - pid_t pid = fork(); - - // Child. - if (pid == 0) { - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0); - MaybeSave(); - TEST_PCHECK(raise(SIGSTOP) == 0); - MaybeSave(); - _exit(0); - } - - // Parent. - int status; - memset(&iov, 0, sizeof(iov)); - ASSERT_THAT(pid, SyscallSucceeds()); // Might still be < 0. - ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); - - iov.iov_base = ®s_; - iov.iov_len = sizeof(regs_); - ASSERT_THAT(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov), - SyscallSucceeds()); - - child_ = pid; - } - - void Detach() { - ASSERT_THAT(ptrace(PTRACE_DETACH, child_, 0, 0), SyscallSucceeds()); - } - - void SetRip(uint64_t newrip) { -#if defined(__x86_64__) - regs_.rip = newrip; -#elif defined(__aarch64__) - regs_.pc = newrip; -#else -#error "Unknown architecture" -#endif - ASSERT_THAT(ptrace(PTRACE_SETREGSET, child_, NT_PRSTATUS, &iov), - SyscallSucceeds()); - } - - void SetRsp(uint64_t newrsp) { -#if defined(__x86_64__) - regs_.rsp = newrsp; -#elif defined(__aarch64__) - regs_.sp = newrsp; -#else -#error "Unknown architecture" -#endif - ASSERT_THAT(ptrace(PTRACE_SETREGSET, child_, NT_PRSTATUS, &iov), - SyscallSucceeds()); - } - - // Wait waits for the child pid and returns the exit status. - int Wait() { - int status; - while (true) { - int rval = wait4(child_, &status, 0, NULL); - if (rval < 0) { - return rval; - } - if (rval == child_) { - return status; - } - } - } -}; - -TEST_F(SysretTest, JustDetach) { - Detach(); - int status = Wait(); - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "status = " << status; -} - -TEST_F(SysretTest, BadRip) { - SetRip(kNonCanonicalRip); - Detach(); - int status = Wait(); - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) - << "status = " << status; -} - -TEST_F(SysretTest, BadRsp) { - SetRsp(kNonCanonicalRsp); - Detach(); - int status = Wait(); -#if defined(__x86_64__) - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGBUS) - << "status = " << status; -#elif defined(__aarch64__) - EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) - << "status = " << status; -#else -#error "Unknown architecture" -#endif -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc deleted file mode 100644 index f56c50e61..000000000 --- a/test/syscalls/linux/tcp_socket.cc +++ /dev/null @@ -1,2076 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#ifdef __linux__ -#include <linux/filter.h> -#endif // __linux__ -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <unistd.h> - -#include <limits> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - addr.ss_family = family; - switch (family) { - case AF_INET: - reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr = - htonl(INADDR_LOOPBACK); - break; - case AF_INET6: - reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr = - in6addr_loopback; - break; - default: - return PosixError(EINVAL, - absl::StrCat("unknown socket family: ", family)); - } - return addr; -} - -static void FillSocketBuffers(int sender, int receiver) { - // Set the FD to O_NONBLOCK. - int opts; - int orig_opts; - ASSERT_THAT(opts = fcntl(sender, F_GETFL), SyscallSucceeds()); - orig_opts = opts; - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(sender, F_SETFL, opts), SyscallSucceeds()); - - // Set TCP_NODELAY, which will cause linux to fill the receive buffer from the - // send buffer as quickly as possibly. This way we can fill up both buffers - // faster. - constexpr int tcp_nodelay_flag = 1; - ASSERT_THAT(setsockopt(sender, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay_flag, - sizeof(tcp_nodelay_flag)), - SyscallSucceeds()); - - // Set a 256KB send/receive buffer. - int buf_sz = 1 << 18; - EXPECT_THAT( - setsockopt(receiver, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)), - SyscallSucceedsWithValue(0)); - EXPECT_THAT( - setsockopt(sender, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz)), - SyscallSucceedsWithValue(0)); - - // Create a large buffer that will be used for sending. - std::vector<char> buf(1 << 16); - - // Write until we receive an error. - while (RetryEINTR(send)(sender, buf.data(), buf.size(), 0) != -1) { - // Sleep to give linux a chance to move data from the send buffer to the - // receive buffer. - usleep(10000); // 10ms. - } - // The last error should have been EWOULDBLOCK. - ASSERT_EQ(errno, EWOULDBLOCK); - - // Restore the fcntl opts - ASSERT_THAT(fcntl(sender, F_SETFL, orig_opts), SyscallSucceeds()); -} - -// Fixture for tests parameterized by the address family to use (AF_INET and -// AF_INET6) when creating sockets. -class TcpSocketTest : public ::testing::TestWithParam<int> { - protected: - // Creates three sockets that will be used by test cases -- a listener, one - // that connects, and the accepted one. - void SetUp() override; - - // Closes the sockets created by SetUp(). - void TearDown() override; - - // Listening socket. - int listener_ = -1; - - // Socket connected via connect(). - int first_fd = -1; - - // Socket connected via accept(). - int second_fd = -1; - - // Initial size of the send buffer. - int sendbuf_size_ = -1; -}; - -void TcpSocketTest::SetUp() { - ASSERT_THAT(listener_ = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - - ASSERT_THAT(first_fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT( - bind(listener_, reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listener_, SOMAXCONN), SyscallSucceeds()); - - // Get the address we're listening on, then connect to it. We need to do this - // because we're allowing the stack to pick a port for us. - ASSERT_THAT(getsockname(listener_, reinterpret_cast<struct sockaddr*>(&addr), - &addrlen), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)( - first_fd, reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Get the initial send buffer size. - socklen_t optlen = sizeof(sendbuf_size_); - ASSERT_THAT( - getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size_, &optlen), - SyscallSucceeds()); - - // Accept the connection. - ASSERT_THAT(second_fd = RetryEINTR(accept)(listener_, nullptr, nullptr), - SyscallSucceeds()); -} - -void TcpSocketTest::TearDown() { - EXPECT_THAT(close(listener_), SyscallSucceeds()); - if (first_fd >= 0) { - EXPECT_THAT(close(first_fd), SyscallSucceeds()); - } - if (second_fd >= 0) { - EXPECT_THAT(close(second_fd), SyscallSucceeds()); - } -} - -TEST_P(TcpSocketTest, ConnectOnEstablishedConnection) { - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - ASSERT_THAT(connect(first_fd, reinterpret_cast<const struct sockaddr*>(&addr), - addrlen), - SyscallFailsWithErrno(EISCONN)); - ASSERT_THAT(connect(second_fd, - reinterpret_cast<const struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EISCONN)); -} - -TEST_P(TcpSocketTest, ShutdownWriteInTimeWait) { - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds()); - EXPECT_THAT(shutdown(first_fd, SHUT_RDWR), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); // Wait to enter TIME_WAIT. - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(TcpSocketTest, ShutdownWriteInFinWait1) { - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds()); - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds()); - absl::SleepFor(absl::Seconds(1)); // Wait to enter FIN-WAIT2. - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds()); -} - -TEST_P(TcpSocketTest, DataCoalesced) { - char buf[10]; - - // Write in two steps. - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf) / 2), - SyscallSucceedsWithValue(sizeof(buf) / 2)); - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf) / 2), - SyscallSucceedsWithValue(sizeof(buf) / 2)); - - // Allow stack to process both packets. - absl::SleepFor(absl::Seconds(1)); - - // Read in one shot. - EXPECT_THAT(RetryEINTR(recv)(second_fd, buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(TcpSocketTest, SenderAddressIgnored) { - char buf[3]; - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - memset(&addr, 0, sizeof(addr)); - - ASSERT_THAT( - RetryEINTR(recvfrom)(second_fd, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceedsWithValue(3)); - - // Check that addr remains zeroed-out. - const char* ptr = reinterpret_cast<char*>(&addr); - for (size_t i = 0; i < sizeof(addr); i++) { - EXPECT_EQ(ptr[i], 0); - } -} - -TEST_P(TcpSocketTest, SenderAddressIgnoredOnPeek) { - char buf[3]; - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - memset(&addr, 0, sizeof(addr)); - - ASSERT_THAT( - RetryEINTR(recvfrom)(second_fd, buf, sizeof(buf), MSG_PEEK, - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceedsWithValue(3)); - - // Check that addr remains zeroed-out. - const char* ptr = reinterpret_cast<char*>(&addr); - for (size_t i = 0; i < sizeof(addr); i++) { - EXPECT_EQ(ptr[i], 0); - } -} - -TEST_P(TcpSocketTest, SendtoAddressIgnored) { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - addr.ss_family = GetParam(); // FIXME(b/63803955) - - char data = '\0'; - EXPECT_THAT( - RetryEINTR(sendto)(first_fd, &data, sizeof(data), 0, - reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), - SyscallSucceedsWithValue(1)); -} - -TEST_P(TcpSocketTest, WritevZeroIovec) { - // 2 bytes just to be safe and have vecs[1] not point to something random - // (even though length is 0). - char buf[2]; - char recv_buf[1]; - - // Construct a vec where the final vector is of length 0. - iovec vecs[2] = {}; - vecs[0].iov_base = buf; - vecs[0].iov_len = 1; - vecs[1].iov_base = buf + 1; - vecs[1].iov_len = 0; - - EXPECT_THAT(RetryEINTR(writev)(first_fd, vecs, 2), - SyscallSucceedsWithValue(1)); - - EXPECT_THAT(RetryEINTR(recv)(second_fd, recv_buf, 1, 0), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(memcmp(recv_buf, buf, 1), 0); -} - -TEST_P(TcpSocketTest, ZeroWriteAllowed) { - char buf[3]; - // Send a zero length packet. - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, 0), SyscallSucceedsWithValue(0)); - // Verify that there is no packet available. - EXPECT_THAT(RetryEINTR(recv)(second_fd, buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); -} - -// Test that a non-blocking write with a buffer that is larger than the send -// buffer size will not actually write the whole thing at once. Regression test -// for b/64438887. -TEST_P(TcpSocketTest, NonblockingLargeWrite) { - // Set the FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds()); - - // Allocate a buffer three times the size of the send buffer. We do this with - // a vector to avoid allocating on the stack. - int size = 3 * sendbuf_size_; - std::vector<char> buf(size); - - // Try to write the whole thing. - int n; - ASSERT_THAT(n = RetryEINTR(write)(first_fd, buf.data(), size), - SyscallSucceeds()); - - // We should have written something, but not the whole thing. - EXPECT_GT(n, 0); - EXPECT_LT(n, size); -} - -// Test that a blocking write with a buffer that is larger than the send buffer -// will block until the entire buffer is sent. -TEST_P(TcpSocketTest, BlockingLargeWrite_NoRandomSave) { - // Allocate a buffer three times the size of the send buffer on the heap. We - // do this as a vector to avoid allocating on the stack. - int size = 3 * sendbuf_size_; - std::vector<char> writebuf(size); - - // Start reading the response in a loop. - int read_bytes = 0; - ScopedThread t([this, &read_bytes]() { - // Avoid interrupting the blocking write in main thread. - const DisableSave disable_save; - - // Take ownership of the FD so that we close it on failure. This will - // unblock the blocking write below. - FileDescriptor fd(second_fd); - second_fd = -1; - - char readbuf[2500] = {}; - int n = -1; - while (n != 0) { - ASSERT_THAT(n = RetryEINTR(read)(fd.get(), &readbuf, sizeof(readbuf)), - SyscallSucceeds()); - read_bytes += n; - } - }); - - // Try to write the whole thing. - int n; - ASSERT_THAT(n = WriteFd(first_fd, writebuf.data(), size), SyscallSucceeds()); - - // We should have written the whole thing. - EXPECT_EQ(n, size); - EXPECT_THAT(close(first_fd), SyscallSucceedsWithValue(0)); - first_fd = -1; - t.Join(); - - // We should have read the whole thing. - EXPECT_EQ(read_bytes, size); -} - -// Test that a send with MSG_DONTWAIT flag and buffer that larger than the send -// buffer size will not write the whole thing. -TEST_P(TcpSocketTest, LargeSendDontWait) { - // Allocate a buffer three times the size of the send buffer. We do this on - // with a vector to avoid allocating on the stack. - int size = 3 * sendbuf_size_; - std::vector<char> buf(size); - - // Try to write the whole thing with MSG_DONTWAIT flag, which can - // return a partial write. - int n; - ASSERT_THAT(n = RetryEINTR(send)(first_fd, buf.data(), size, MSG_DONTWAIT), - SyscallSucceeds()); - - // We should have written something, but not the whole thing. - EXPECT_GT(n, 0); - EXPECT_LT(n, size); -} - -// Test that a send on a non-blocking socket with a buffer that larger than the -// send buffer will not write the whole thing at once. -TEST_P(TcpSocketTest, NonblockingLargeSend) { - // Set the FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds()); - - // Allocate a buffer three times the size of the send buffer. We do this on - // with a vector to avoid allocating on the stack. - int size = 3 * sendbuf_size_; - std::vector<char> buf(size); - - // Try to write the whole thing. - int n; - ASSERT_THAT(n = RetryEINTR(send)(first_fd, buf.data(), size, 0), - SyscallSucceeds()); - - // We should have written something, but not the whole thing. - EXPECT_GT(n, 0); - EXPECT_LT(n, size); -} - -// Same test as above, but calls send instead of write. -TEST_P(TcpSocketTest, BlockingLargeSend_NoRandomSave) { - // Allocate a buffer three times the size of the send buffer. We do this on - // with a vector to avoid allocating on the stack. - int size = 3 * sendbuf_size_; - std::vector<char> writebuf(size); - - // Start reading the response in a loop. - int read_bytes = 0; - ScopedThread t([this, &read_bytes]() { - // Avoid interrupting the blocking write in main thread. - const DisableSave disable_save; - - // Take ownership of the FD so that we close it on failure. This will - // unblock the blocking write below. - FileDescriptor fd(second_fd); - second_fd = -1; - - char readbuf[2500] = {}; - int n = -1; - while (n != 0) { - ASSERT_THAT(n = RetryEINTR(read)(fd.get(), &readbuf, sizeof(readbuf)), - SyscallSucceeds()); - read_bytes += n; - } - }); - - // Try to send the whole thing. - int n; - ASSERT_THAT(n = SendFd(first_fd, writebuf.data(), size, 0), - SyscallSucceeds()); - - // We should have written the whole thing. - EXPECT_EQ(n, size); - EXPECT_THAT(close(first_fd), SyscallSucceedsWithValue(0)); - first_fd = -1; - t.Join(); - - // We should have read the whole thing. - EXPECT_EQ(read_bytes, size); -} - -// Test that polling on a socket with a full send buffer will block. -TEST_P(TcpSocketTest, PollWithFullBufferBlocks) { - FillSocketBuffers(first_fd, second_fd); - // Now polling on the FD with a timeout should return 0 corresponding to no - // FDs ready. - struct pollfd poll_fd = {first_fd, POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), SyscallSucceedsWithValue(0)); -} - -TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) { - FillSocketBuffers(first_fd, second_fd); - constexpr int timeout = 10; - struct timeval tv = {.tv_sec = timeout, .tv_usec = 0}; - EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - struct timespec begin; - struct timespec end; - const DisableSave disable_save; // Timing-related. - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds()); - - ScopedThread send_thread([this]() { - char send_byte; - // Expect the send() to be blocked until receive timeout. - ASSERT_THAT(RetryEINTR(send)(first_fd, &send_byte, sizeof(send_byte), 0), - SyscallFailsWithErrno(EAGAIN)); - }); - - // Wait for the thread to be blocked on write. - absl::SleepFor(absl::Milliseconds(250)); - // Socket close does not have any effect on a blocked write. - ASSERT_THAT(close(first_fd), SyscallSucceeds()); - // Indicate to the cleanup routine that we are already closed. - first_fd = -1; - - send_thread.Join(); - - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds()); - // Check the lower bound on the timeout. Checking for an upper bound is - // fragile because Linux can overrun the timeout due to scheduling delays. - EXPECT_GT(ms_elapsed(begin, end), timeout * 1000 - 1); -} - -TEST_P(TcpSocketTest, ClosedReadBlockingSocket) { - constexpr int timeout = 10; - struct timeval tv = {.tv_sec = timeout, .tv_usec = 0}; - EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), - SyscallSucceeds()); - - struct timespec begin; - struct timespec end; - const DisableSave disable_save; // Timing-related. - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds()); - - ScopedThread read_thread([this]() { - char read_byte; - // Expect the read() to be blocked until receive timeout. - ASSERT_THAT(read(first_fd, &read_byte, sizeof(read_byte)), - SyscallFailsWithErrno(EAGAIN)); - }); - - // Wait for the thread to be blocked on read. - absl::SleepFor(absl::Milliseconds(250)); - // Socket close does not have any effect on a blocked read. - ASSERT_THAT(close(first_fd), SyscallSucceeds()); - // Indicate to the cleanup routine that we are already closed. - first_fd = -1; - - read_thread.Join(); - - EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds()); - // Check the lower bound on the timeout. Checking for an upper bound is - // fragile because Linux can overrun the timeout due to scheduling delays. - EXPECT_GT(ms_elapsed(begin, end), timeout * 1000 - 1); -} - -TEST_P(TcpSocketTest, MsgTrunc) { - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data, - sizeof(received_data) / 2, MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - // Check that we didn't get anything. - char zeros[sizeof(received_data)] = {}; - EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data))); -} - -// MSG_CTRUNC is a return flag but linux allows it to be set on input flags -// without returning an error. -TEST_P(TcpSocketTest, MsgTruncWithCtrunc) { - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT( - RetryEINTR(recv)(second_fd, received_data, sizeof(received_data) / 2, - MSG_TRUNC | MSG_CTRUNC), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - // Check that we didn't get anything. - char zeros[sizeof(received_data)] = {}; - EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data))); -} - -// This test will verify that MSG_CTRUNC doesn't do anything when specified -// on input. -TEST_P(TcpSocketTest, MsgTruncWithCtruncOnly) { - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data, - sizeof(received_data) / 2, MSG_CTRUNC), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - // Since MSG_CTRUNC here had no affect, it should not behave like MSG_TRUNC. - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data) / 2)); -} - -TEST_P(TcpSocketTest, MsgTruncLargeSize) { - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data) * 2] = {}; - ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data, sizeof(received_data), - MSG_TRUNC), - SyscallSucceedsWithValue(sizeof(sent_data))); - - // Check that we didn't get anything. - char zeros[sizeof(received_data)] = {}; - EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data))); -} - -TEST_P(TcpSocketTest, MsgTruncPeek) { - char sent_data[512]; - RandomizeBuffer(sent_data, sizeof(sent_data)); - ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - char received_data[sizeof(sent_data)] = {}; - ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data, - sizeof(received_data) / 2, MSG_TRUNC | MSG_PEEK), - SyscallSucceedsWithValue(sizeof(sent_data) / 2)); - - // Check that we didn't get anything. - char zeros[sizeof(received_data)] = {}; - EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data))); - - // Check that we can still get all of the data. - ASSERT_THAT( - RetryEINTR(recv)(second_fd, received_data, sizeof(received_data), 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data))); -} - -TEST_P(TcpSocketTest, NoDelayDefault) { - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -TEST_P(TcpSocketTest, SetNoDelay) { - ASSERT_THAT(setsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOn); - - ASSERT_THAT(setsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &kSockOptOff, - sizeof(kSockOptOff)), - SyscallSucceeds()); - - EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kSockOptOff); -} - -#ifndef TCP_INQ -#define TCP_INQ 36 -#endif - -TEST_P(TcpSocketTest, TcpInqSetSockOpt) { - char buf[1024]; - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - // TCP_INQ is disabled by default. - int val = -1; - socklen_t slen = sizeof(val); - EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen), - SyscallSucceedsWithValue(0)); - ASSERT_EQ(val, 0); - - // Try to set TCP_INQ. - val = 1; - EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - val = -1; - slen = sizeof(val); - EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen), - SyscallSucceedsWithValue(0)); - ASSERT_EQ(val, 1); - - // Try to unset TCP_INQ. - val = 0; - EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - val = -1; - slen = sizeof(val); - EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen), - SyscallSucceedsWithValue(0)); - ASSERT_EQ(val, 0); -} - -TEST_P(TcpSocketTest, TcpInq) { - char buf[1024]; - // Write more than one TCP segment. - int size = sizeof(buf); - int kChunk = sizeof(buf) / 4; - for (int i = 0; i < size; i += kChunk) { - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, kChunk), - SyscallSucceedsWithValue(kChunk)); - } - - int val = 1; - kChunk = sizeof(buf) / 2; - EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - - // Wait when all data will be in the received queue. - while (true) { - ASSERT_THAT(ioctl(second_fd, TIOCINQ, &size), SyscallSucceeds()); - if (size == sizeof(buf)) { - break; - } - absl::SleepFor(absl::Milliseconds(10)); - } - - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(sizeof(int))); - size = sizeof(buf); - struct iovec iov; - for (int i = 0; size != 0; i += kChunk) { - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - iov.iov_base = buf; - iov.iov_len = kChunk; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - ASSERT_THAT(RetryEINTR(recvmsg)(second_fd, &msg, 0), - SyscallSucceedsWithValue(kChunk)); - size -= kChunk; - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_TCP); - ASSERT_EQ(cmsg->cmsg_type, TCP_INQ); - - int inq = 0; - memcpy(&inq, CMSG_DATA(cmsg), sizeof(int)); - ASSERT_EQ(inq, size); - } -} - -TEST_P(TcpSocketTest, Tiocinq) { - char buf[1024]; - size_t size = sizeof(buf); - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, size), - SyscallSucceedsWithValue(size)); - - uint32_t seed = time(nullptr); - const size_t max_chunk = size / 10; - while (size > 0) { - size_t chunk = (rand_r(&seed) % max_chunk) + 1; - ssize_t read = - RetryEINTR(recvfrom)(second_fd, buf, chunk, 0, nullptr, nullptr); - ASSERT_THAT(read, SyscallSucceeds()); - size -= read; - - int inq = 0; - ASSERT_THAT(ioctl(second_fd, TIOCINQ, &inq), SyscallSucceeds()); - ASSERT_EQ(inq, size); - } -} - -TEST_P(TcpSocketTest, TcpSCMPriority) { - char buf[1024]; - ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - int val = 1; - EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - EXPECT_THAT( - setsockopt(second_fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)), - SyscallSucceedsWithValue(0)); - - struct msghdr msg = {}; - std::vector<char> control( - CMSG_SPACE(sizeof(struct timeval) + CMSG_SPACE(sizeof(int)))); - struct iovec iov; - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - ASSERT_THAT(RetryEINTR(recvmsg)(second_fd, &msg, 0), - SyscallSucceedsWithValue(sizeof(buf))); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - // TODO(b/78348848): SO_TIMESTAMP isn't implemented for TCP sockets. - if (!IsRunningOnGvisor() || cmsg->cmsg_level == SOL_SOCKET) { - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SO_TIMESTAMP); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct timeval))); - - cmsg = CMSG_NXTHDR(&msg, cmsg); - ASSERT_NE(cmsg, nullptr); - } - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_TCP); - ASSERT_EQ(cmsg->cmsg_type, TCP_INQ); - - int inq = 0; - memcpy(&inq, CMSG_DATA(cmsg), sizeof(int)); - ASSERT_EQ(inq, 0); - - cmsg = CMSG_NXTHDR(&msg, cmsg); - ASSERT_EQ(cmsg, nullptr); -} - -TEST_P(TcpSocketTest, TimeWaitPollHUP) { - shutdown(first_fd, SHUT_RDWR); - ScopedThread t([&]() { - constexpr int kTimeout = 10000; - constexpr int16_t want_events = POLLHUP; - struct pollfd pfd = { - .fd = first_fd, - .events = want_events, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - }); - shutdown(second_fd, SHUT_RDWR); - t.Join(); - // At this point first_fd should be in TIME-WAIT and polling for POLLHUP - // should return with 1 FD. - constexpr int kTimeout = 10000; - constexpr int16_t want_events = POLLHUP; - struct pollfd pfd = { - .fd = first_fd, - .events = want_events, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); -} - -INSTANTIATE_TEST_SUITE_P(AllInetTests, TcpSocketTest, - ::testing::Values(AF_INET, AF_INET6)); - -// Fixture for tests parameterized by address family that don't want the fixture -// to do things. -using SimpleTcpSocketTest = ::testing::TestWithParam<int>; - -TEST_P(SimpleTcpSocketTest, SendUnconnected) { - int fd; - ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - FileDescriptor sock_fd(fd); - - char data = '\0'; - EXPECT_THAT(RetryEINTR(send)(fd, &data, sizeof(data), 0), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(SimpleTcpSocketTest, SendtoWithoutAddressUnconnected) { - int fd; - ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - FileDescriptor sock_fd(fd); - - char data = '\0'; - EXPECT_THAT(RetryEINTR(sendto)(fd, &data, sizeof(data), 0, nullptr, 0), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(SimpleTcpSocketTest, SendtoWithAddressUnconnected) { - int fd; - ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - FileDescriptor sock_fd(fd); - - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - char data = '\0'; - EXPECT_THAT( - RetryEINTR(sendto)(fd, &data, sizeof(data), 0, - reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EPIPE)); -} - -TEST_P(SimpleTcpSocketTest, GetPeerNameUnconnected) { - int fd; - ASSERT_THAT(fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP), - SyscallSucceeds()); - FileDescriptor sock_fd(fd); - - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT(getpeername(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(TcpSocketTest, FullBuffer) { - // Set both FDs to be blocking. - int flags = 0; - ASSERT_THAT(flags = fcntl(first_fd, F_GETFL), SyscallSucceeds()); - EXPECT_THAT(fcntl(first_fd, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds()); - flags = 0; - ASSERT_THAT(flags = fcntl(second_fd, F_GETFL), SyscallSucceeds()); - EXPECT_THAT(fcntl(second_fd, F_SETFL, flags & ~O_NONBLOCK), - SyscallSucceeds()); - - // 2500 was chosen as a small value that can be set on Linux. - int set_snd = 2500; - EXPECT_THAT( - setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &set_snd, sizeof(set_snd)), - SyscallSucceedsWithValue(0)); - int get_snd = -1; - socklen_t get_snd_len = sizeof(get_snd); - EXPECT_THAT( - getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &get_snd, &get_snd_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_snd_len, sizeof(get_snd)); - EXPECT_GT(get_snd, 0); - - // 2500 was chosen as a small value that can be set on Linux and gVisor. - int set_rcv = 2500; - EXPECT_THAT( - setsockopt(second_fd, SOL_SOCKET, SO_RCVBUF, &set_rcv, sizeof(set_rcv)), - SyscallSucceedsWithValue(0)); - int get_rcv = -1; - socklen_t get_rcv_len = sizeof(get_rcv); - EXPECT_THAT( - getsockopt(second_fd, SOL_SOCKET, SO_RCVBUF, &get_rcv, &get_rcv_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_rcv_len, sizeof(get_rcv)); - EXPECT_GE(get_rcv, 2500); - - // Quick sanity test. - EXPECT_LT(get_snd + get_rcv, 2500 * IOV_MAX); - - char data[2500] = {}; - std::vector<struct iovec> iovecs; - for (int i = 0; i < IOV_MAX; i++) { - struct iovec iov = {}; - iov.iov_base = data; - iov.iov_len = sizeof(data); - iovecs.push_back(iov); - } - ScopedThread t([this, &iovecs]() { - int result = -1; - EXPECT_THAT( - result = RetryEINTR(writev)(first_fd, iovecs.data(), iovecs.size()), - SyscallSucceeds()); - EXPECT_GT(result, 1); - EXPECT_LT(result, sizeof(data) * iovecs.size()); - }); - - char recv = 0; - EXPECT_THAT(RetryEINTR(read)(second_fd, &recv, 1), - SyscallSucceedsWithValue(1)); - EXPECT_THAT(close(second_fd), SyscallSucceedsWithValue(0)); - second_fd = -1; -} - -TEST_P(TcpSocketTest, PollAfterShutdown) { - ScopedThread client_thread([this]() { - EXPECT_THAT(shutdown(first_fd, SHUT_WR), SyscallSucceedsWithValue(0)); - struct pollfd poll_fd = {first_fd, POLLIN | POLLERR | POLLHUP, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); - }); - - EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceedsWithValue(0)); - struct pollfd poll_fd = {second_fd, POLLIN | POLLERR | POLLHUP, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnectRetry) { - const FileDescriptor listener = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - // Bind to some port but don't listen yet. - ASSERT_THAT( - bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Get the address we're bound to, then connect to it. We need to do this - // because we're allowing the stack to pick a port for us. - ASSERT_THAT(getsockname(listener.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - FileDescriptor connector = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Verify that connect fails. - ASSERT_THAT( - RetryEINTR(connect)(connector.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNREFUSED)); - - // Now start listening - ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds()); - - // TODO(gvisor.dev/issue/3828): Issuing connect() again on a socket that - // failed first connect should succeed. - if (IsRunningOnGvisor()) { - ASSERT_THAT( - RetryEINTR(connect)(connector.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNABORTED)); - return; - } - - // Verify that connect now succeeds. - ASSERT_THAT( - RetryEINTR(connect)(connector.get(), - reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Accept the connection. - const FileDescriptor accepted = - ASSERT_NO_ERRNO_AND_VALUE(Accept(listener.get(), nullptr, nullptr)); -} - -// nonBlockingConnectNoListener returns a socket on which a connect that is -// expected to fail has been issued. -PosixErrorOr<FileDescriptor> nonBlockingConnectNoListener(const int family, - sockaddr_storage addr, - socklen_t addrlen) { - // We will first create a socket and bind to ensure we bind a port but will - // not call listen on this socket. - // Then we will create a new socket that will connect to the port bound by - // the first socket and that shoud fail. - constexpr int sock_type = SOCK_STREAM | SOCK_NONBLOCK; - int b_sock; - RETURN_ERROR_IF_SYSCALL_FAIL(b_sock = socket(family, sock_type, IPPROTO_TCP)); - FileDescriptor b(b_sock); - EXPECT_THAT(bind(b.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - // Get the address bound by the listening socket. - EXPECT_THAT( - getsockname(b.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - // Now create another socket and issue a connect on this one. This connect - // should fail as there is no listener. - int c_sock; - RETURN_ERROR_IF_SYSCALL_FAIL(c_sock = socket(family, sock_type, IPPROTO_TCP)); - FileDescriptor s(c_sock); - - // Now connect to the bound address and this should fail as nothing - // is listening on the bound address. - EXPECT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EINPROGRESS)); - - // Wait for the connect to fail. - struct pollfd poll_fd = {s.get(), POLLERR, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1)); - return std::move(s); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - const FileDescriptor s = - nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); - - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - ASSERT_THAT(optlen, sizeof(err)); - EXPECT_EQ(err, ECONNREFUSED); - - unsigned char c; - ASSERT_THAT(read(s.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0)); - int opts; - EXPECT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - opts &= ~O_NONBLOCK; - EXPECT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); - // Try connecting again. - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNABORTED)); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerRead) { - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - const FileDescriptor s = - nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); - - unsigned char c; - ASSERT_THAT(read(s.get(), &c, 1), SyscallFailsWithErrno(ECONNREFUSED)); - ASSERT_THAT(read(s.get(), &c, 1), SyscallSucceedsWithValue(0)); - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNABORTED)); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerPeek) { - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - const FileDescriptor s = - nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); - - unsigned char c; - ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), - SyscallFailsWithErrno(ECONNREFUSED)); - ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), SyscallSucceedsWithValue(0)); - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNABORTED)); -} - -TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - const FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - ASSERT_THAT( - (bind)(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - // Get the bound port. - ASSERT_THAT( - getsockname(s.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - constexpr int kBufSz = 1 << 20; // 1 MiB - std::vector<char> writebuf(kBufSz); - - // Start reading the response in a loop. - int read_bytes = 0; - ScopedThread t([&s, &read_bytes]() { - // Too many syscalls. - const DisableSave disable_save; - - char readbuf[2500] = {}; - int n = -1; - while (n != 0) { - ASSERT_THAT(n = RetryEINTR(read)(s.get(), &readbuf, sizeof(readbuf)), - SyscallSucceeds()); - read_bytes += n; - } - }); - - // Try to send the whole thing. - int n; - ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), kBufSz, 0), - SyscallSucceeds()); - - // We should have written the whole thing. - EXPECT_EQ(n, kBufSz); - EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); - t.Join(); - - // We should have read the whole thing. - EXPECT_EQ(read_bytes, kBufSz); -} - -TEST_P(SimpleTcpSocketTest, SelfConnectSend_NoRandomSave) { - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - const FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - constexpr int max_seg = 256; - ASSERT_THAT( - setsockopt(s.get(), SOL_TCP, TCP_MAXSEG, &max_seg, sizeof(max_seg)), - SyscallSucceeds()); - - ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - // Get the bound port. - ASSERT_THAT( - getsockname(s.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - std::vector<char> writebuf(512 << 10); // 512 KiB. - - // Try to send the whole thing. - int n; - ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), writebuf.size(), 0), - SyscallSucceeds()); - - // We should have written the whole thing. - EXPECT_EQ(n, writebuf.size()); - EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnect) { - const FileDescriptor listener = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT( - bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds()); - - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Set the FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); - - ASSERT_THAT(getsockname(listener.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EINPROGRESS)); - - int t; - ASSERT_THAT(t = RetryEINTR(accept)(listener.get(), nullptr, nullptr), - SyscallSucceeds()); - - // Now polling on the FD with a timeout should return 0 corresponding to no - // FDs ready. - struct pollfd poll_fd = {s.get(), POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); - - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - - EXPECT_EQ(err, 0); - - EXPECT_THAT(close(t), SyscallSucceeds()); -} - -TEST_P(SimpleTcpSocketTest, NonBlockingConnectRemoteClose) { - const FileDescriptor listener = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT( - bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds()); - - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - - ASSERT_THAT(getsockname(listener.get(), - reinterpret_cast<struct sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EINPROGRESS)); - - int t; - ASSERT_THAT(t = RetryEINTR(accept)(listener.get(), nullptr, nullptr), - SyscallSucceeds()); - - EXPECT_THAT(close(t), SyscallSucceeds()); - - // Now polling on the FD with a timeout should return 0 corresponding to no - // FDs ready. - struct pollfd poll_fd = {s.get(), POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EISCONN)); -} - -// Test that we get an ECONNREFUSED with a blocking socket when no one is -// listening on the other end. -TEST_P(SimpleTcpSocketTest, BlockingConnectRefused) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(ECONNREFUSED)); - - // Avoiding triggering save in destructor of s. - EXPECT_THAT(close(s.release()), SyscallSucceeds()); -} - -// Test that connecting to a non-listening port and thus receiving a RST is -// handled appropriately by the socket - the port that the socket was bound to -// is released and the expected error is returned. -TEST_P(SimpleTcpSocketTest, CleanupOnConnectionRefused) { - // Create a socket that is known to not be listening. As is it bound but not - // listening, when another socket connects to the port, it will refuse.. - FileDescriptor bound_s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - sockaddr_storage bound_addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t bound_addrlen = sizeof(bound_addr); - - ASSERT_THAT( - bind(bound_s.get(), reinterpret_cast<struct sockaddr*>(&bound_addr), - bound_addrlen), - SyscallSucceeds()); - - // Get the addresses the socket is bound to because the port is chosen by the - // stack. - ASSERT_THAT(getsockname(bound_s.get(), - reinterpret_cast<struct sockaddr*>(&bound_addr), - &bound_addrlen), - SyscallSucceeds()); - - // Create, initialize, and bind the socket that is used to test connecting to - // the non-listening port. - FileDescriptor client_s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - // Initialize client address to the loopback one. - sockaddr_storage client_addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t client_addrlen = sizeof(client_addr); - - ASSERT_THAT( - bind(client_s.get(), reinterpret_cast<struct sockaddr*>(&client_addr), - client_addrlen), - SyscallSucceeds()); - - ASSERT_THAT(getsockname(client_s.get(), - reinterpret_cast<struct sockaddr*>(&client_addr), - &client_addrlen), - SyscallSucceeds()); - - // Now the test: connect to the bound but not listening socket with the - // client socket. The bound socket should return a RST and cause the client - // socket to return an error and clean itself up immediately. - // The error being ECONNREFUSED diverges with RFC 793, page 37, but does what - // Linux does. - ASSERT_THAT(connect(client_s.get(), - reinterpret_cast<const struct sockaddr*>(&bound_addr), - bound_addrlen), - SyscallFailsWithErrno(ECONNREFUSED)); - - FileDescriptor new_s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Test binding to the address from the client socket. This should be okay - // if it was dropped correctly. - ASSERT_THAT( - bind(new_s.get(), reinterpret_cast<struct sockaddr*>(&client_addr), - client_addrlen), - SyscallSucceeds()); - - // Attempt #2, with the new socket and reused addr our connect should fail in - // the same way as before, not with an EADDRINUSE. - // - // TODO(gvisor.dev/issue/3828): 2nd connect on a socket which failed connect - // first time should succeed. - // gVisor never issues the second connect and returns ECONNABORTED instead. - // Linux actually sends a SYN again and gets a RST and correctly returns - // ECONNREFUSED. - if (IsRunningOnGvisor()) { - ASSERT_THAT(connect(client_s.get(), - reinterpret_cast<const struct sockaddr*>(&bound_addr), - bound_addrlen), - SyscallFailsWithErrno(ECONNABORTED)); - return; - } - ASSERT_THAT(connect(client_s.get(), - reinterpret_cast<const struct sockaddr*>(&bound_addr), - bound_addrlen), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -// Test that we get an ECONNREFUSED with a nonblocking socket. -TEST_P(SimpleTcpSocketTest, NonBlockingConnectRefused) { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - ASSERT_THAT(RetryEINTR(connect)( - s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallFailsWithErrno(EINPROGRESS)); - - // We don't need to specify any events to get POLLHUP or POLLERR as these - // are added before the poll. - struct pollfd poll_fd = {s.get(), /*events=*/0, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1)); - - // The ECONNREFUSED should cause us to be woken up with POLLHUP. - EXPECT_NE(poll_fd.revents & (POLLHUP | POLLERR), 0); - - // Avoiding triggering save in destructor of s. - EXPECT_THAT(close(s.release()), SyscallSucceeds()); -} - -// Test that setting a supported congestion control algorithm succeeds for an -// unconnected TCP socket -TEST_P(SimpleTcpSocketTest, SetCongestionControlSucceedsForSupported) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - { - const char kSetCC[kTcpCaNameMax] = "reno"; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC, - strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax]; - memset(got_cc, '1', sizeof(got_cc)); - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - // We ignore optlen here as the linux kernel sets optlen to the lower of the - // size of the buffer passed in or kTcpCaNameMax and not the length of the - // congestion control algorithm's actual name. - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kTcpCaNameMax))); - } - { - const char kSetCC[kTcpCaNameMax] = "cubic"; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC, - strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax]; - memset(got_cc, '1', sizeof(got_cc)); - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - // We ignore optlen here as the linux kernel sets optlen to the lower of the - // size of the buffer passed in or kTcpCaNameMax and not the length of the - // congestion control algorithm's actual name. - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kTcpCaNameMax))); - } -} - -// This test verifies that a getsockopt(...TCP_CONGESTION) behaviour is -// consistent between linux and gvisor when the passed in buffer is smaller than -// kTcpCaNameMax. -TEST_P(SimpleTcpSocketTest, SetGetTCPCongestionShortReadBuffer) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - { - // Verify that getsockopt/setsockopt work with buffers smaller than - // kTcpCaNameMax. - const char kSetCC[] = "cubic"; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC, - strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[sizeof(kSetCC)]; - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(sizeof(got_cc), optlen); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(got_cc))); - } -} - -// This test verifies that a getsockopt(...TCP_CONGESTION) behaviour is -// consistent between linux and gvisor when the passed in buffer is larger than -// kTcpCaNameMax. -TEST_P(SimpleTcpSocketTest, SetGetTCPCongestionLargeReadBuffer) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - { - // Verify that getsockopt works with buffers larger than - // kTcpCaNameMax. - const char kSetCC[] = "cubic"; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &kSetCC, - strlen(kSetCC)), - SyscallSucceedsWithValue(0)); - - char got_cc[kTcpCaNameMax + 5]; - socklen_t optlen = sizeof(got_cc); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - // Linux copies the minimum of kTcpCaNameMax or the length of the passed in - // buffer and sets optlen to the number of bytes actually copied - // irrespective of the actual length of the congestion control name. - EXPECT_EQ(kTcpCaNameMax, optlen); - EXPECT_EQ(0, memcmp(got_cc, kSetCC, sizeof(kSetCC))); - } -} - -// Test that setting an unsupported congestion control algorithm fails for an -// unconnected TCP socket. -TEST_P(SimpleTcpSocketTest, SetCongestionControlFailsForUnsupported) { - // This is Linux's net/tcp.h TCP_CA_NAME_MAX. - const int kTcpCaNameMax = 16; - - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - char old_cc[kTcpCaNameMax]; - socklen_t optlen = sizeof(old_cc); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &old_cc, &optlen), - SyscallSucceedsWithValue(0)); - - const char kSetCC[] = "invalid_ca_kSetCC"; - ASSERT_THAT( - setsockopt(s.get(), SOL_TCP, TCP_CONGESTION, &kSetCC, strlen(kSetCC)), - SyscallFailsWithErrno(ENOENT)); - - char got_cc[kTcpCaNameMax]; - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_CONGESTION, &got_cc, &optlen), - SyscallSucceedsWithValue(0)); - // We ignore optlen here as the linux kernel sets optlen to the lower of the - // size of the buffer passed in or kTcpCaNameMax and not the length of the - // congestion control algorithm's actual name. - EXPECT_EQ(0, memcmp(got_cc, old_cc, sizeof(kTcpCaNameMax))); -} - -TEST_P(SimpleTcpSocketTest, MaxSegDefault) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - constexpr int kDefaultMSS = 536; - int tcp_max_seg; - socklen_t optlen = sizeof(tcp_max_seg); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg, &optlen), - SyscallSucceedsWithValue(0)); - - EXPECT_EQ(kDefaultMSS, tcp_max_seg); - EXPECT_EQ(sizeof(tcp_max_seg), optlen); -} - -TEST_P(SimpleTcpSocketTest, SetMaxSeg) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - constexpr int kDefaultMSS = 536; - constexpr int kTCPMaxSeg = 1024; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &kTCPMaxSeg, - sizeof(kTCPMaxSeg)), - SyscallSucceedsWithValue(0)); - - // Linux actually never returns the user_mss value. It will always return the - // default MSS value defined above for an unconnected socket and always return - // the actual current MSS for a connected one. - int optval; - socklen_t optlen = sizeof(optval); - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &optval, &optlen), - SyscallSucceedsWithValue(0)); - - EXPECT_EQ(kDefaultMSS, optval); - EXPECT_EQ(sizeof(optval), optlen); -} - -TEST_P(SimpleTcpSocketTest, SetMaxSegFailsForInvalidMSSValues) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - { - constexpr int tcp_max_seg = 10; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg, - sizeof(tcp_max_seg)), - SyscallFailsWithErrno(EINVAL)); - } - { - constexpr int tcp_max_seg = 75000; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_MAXSEG, &tcp_max_seg, - sizeof(tcp_max_seg)), - SyscallFailsWithErrno(EINVAL)); - } -} - -TEST_P(SimpleTcpSocketTest, SetTCPUserTimeout) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - { - constexpr int kTCPUserTimeout = -1; - EXPECT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kTCPUserTimeout, sizeof(kTCPUserTimeout)), - SyscallFailsWithErrno(EINVAL)); - } - - // kTCPUserTimeout is in milliseconds. - constexpr int kTCPUserTimeout = 100; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, - &kTCPUserTimeout, sizeof(kTCPUserTimeout)), - SyscallSucceedsWithValue(0)); - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_USER_TIMEOUT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kTCPUserTimeout); -} - -TEST_P(SimpleTcpSocketTest, SetTCPDeferAcceptNeg) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // -ve TCP_DEFER_ACCEPT is same as setting it to zero. - constexpr int kNeg = -1; - EXPECT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &kNeg, sizeof(kNeg)), - SyscallSucceeds()); - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 0); -} - -TEST_P(SimpleTcpSocketTest, GetTCPDeferAcceptDefault) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, 0); -} - -TEST_P(SimpleTcpSocketTest, SetTCPDeferAcceptGreaterThanZero) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - // kTCPDeferAccept is in seconds. - // NOTE: linux translates seconds to # of retries and back from - // #of retries to seconds. Which means only certain values - // translate back exactly. That's why we use 3 here, a value of - // 5 will result in us getting back 7 instead of 5 in the - // getsockopt. - constexpr int kTCPDeferAccept = 3; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, - &kTCPDeferAccept, sizeof(kTCPDeferAccept)), - SyscallSucceeds()); - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_DEFER_ACCEPT, &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kTCPDeferAccept); -} - -TEST_P(SimpleTcpSocketTest, RecvOnClosedSocket) { - auto s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - char buf[1]; - EXPECT_THAT(recv(s.get(), buf, 0, 0), SyscallFailsWithErrno(ENOTCONN)); - EXPECT_THAT(recv(s.get(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(SimpleTcpSocketTest, TCPConnectSoRcvBufRace) { - auto s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - RetryEINTR(connect)(s.get(), reinterpret_cast<struct sockaddr*>(&addr), - addrlen); - int buf_sz = 1 << 18; - EXPECT_THAT( - setsockopt(s.get(), SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(SimpleTcpSocketTest, SetTCPSynCntLessThanOne) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - int default_syn_cnt = get; - - { - // TCP_SYNCNT less than 1 should be rejected with an EINVAL. - constexpr int kZero = 0; - EXPECT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &kZero, sizeof(kZero)), - SyscallFailsWithErrno(EINVAL)); - - // TCP_SYNCNT less than 1 should be rejected with an EINVAL. - constexpr int kNeg = -1; - EXPECT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &kNeg, sizeof(kNeg)), - SyscallFailsWithErrno(EINVAL)); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(default_syn_cnt, get); - } -} - -TEST_P(SimpleTcpSocketTest, GetTCPSynCntDefault) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - int get = -1; - socklen_t get_len = sizeof(get); - constexpr int kDefaultSynCnt = 6; - - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kDefaultSynCnt); -} - -TEST_P(SimpleTcpSocketTest, SetTCPSynCntGreaterThanOne) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - constexpr int kTCPSynCnt = 20; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &kTCPSynCnt, - sizeof(kTCPSynCnt)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kTCPSynCnt); -} - -TEST_P(SimpleTcpSocketTest, SetTCPSynCntAboveMax) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - int default_syn_cnt = get; - { - constexpr int kTCPSynCnt = 256; - ASSERT_THAT(setsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &kTCPSynCnt, - sizeof(kTCPSynCnt)), - SyscallFailsWithErrno(EINVAL)); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), IPPROTO_TCP, TCP_SYNCNT, &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, default_syn_cnt); - } -} - -TEST_P(SimpleTcpSocketTest, SetTCPWindowClampBelowMinRcvBuf) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Discover minimum receive buf by setting a really low value - // for the receive buffer. - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_RCVBUF, &kZero, sizeof(kZero)), - SyscallSucceeds()); - - // Now retrieve the minimum value for SO_RCVBUF as the set above should - // have caused SO_RCVBUF for the socket to be set to the minimum. - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_RCVBUF, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - int min_so_rcvbuf = get; - - { - // TCP_WINDOW_CLAMP less than min_so_rcvbuf/2 should be set to - // min_so_rcvbuf/2. - int below_half_min_rcvbuf = min_so_rcvbuf / 2 - 1; - EXPECT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, - &below_half_min_rcvbuf, sizeof(below_half_min_rcvbuf)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(min_so_rcvbuf / 2, get); - } -} - -TEST_P(SimpleTcpSocketTest, SetTCPWindowClampZeroClosedSocket) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - constexpr int kZero = 0; - ASSERT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, &kZero, sizeof(kZero)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, &get, &get_len), - SyscallSucceeds()); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(get, kZero); -} - -TEST_P(SimpleTcpSocketTest, SetTCPWindowClampAboveHalfMinRcvBuf) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Discover minimum receive buf by setting a really low value - // for the receive buffer. - constexpr int kZero = 0; - EXPECT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_RCVBUF, &kZero, sizeof(kZero)), - SyscallSucceeds()); - - // Now retrieve the minimum value for SO_RCVBUF as the set above should - // have caused SO_RCVBUF for the socket to be set to the minimum. - int get = -1; - socklen_t get_len = sizeof(get); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_RCVBUF, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - int min_so_rcvbuf = get; - - { - int above_half_min_rcv_buf = min_so_rcvbuf / 2 + 1; - EXPECT_THAT( - setsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, - &above_half_min_rcv_buf, sizeof(above_half_min_rcv_buf)), - SyscallSucceeds()); - - int get = -1; - socklen_t get_len = sizeof(get); - - ASSERT_THAT( - getsockopt(s.get(), IPPROTO_TCP, TCP_WINDOW_CLAMP, &get, &get_len), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(get_len, sizeof(get)); - EXPECT_EQ(above_half_min_rcv_buf, get); - } -} - -#ifdef __linux__ - -// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. -// gVisor currently silently ignores attaching a filter. -TEST_P(SimpleTcpSocketTest, SetSocketAttachDetachFilter) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - // Program generated using sudo tcpdump -i lo tcp and port 1234 -dd - struct sock_filter code[] = { - {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd}, - {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000006}, - {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x000004d2}, - {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x000004d2}, - {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017}, - {0x15, 0, 8, 0x00000006}, {0x28, 0, 0, 0x00000014}, - {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, - {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x000004d2}, - {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000004d2}, - {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000}, - }; - struct sock_fprog bpf = { - .len = ABSL_ARRAYSIZE(code), - .filter = code, - }; - ASSERT_THAT( - setsockopt(s.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)), - SyscallSucceeds()); - - constexpr int val = 0; - ASSERT_THAT( - setsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallSucceeds()); -} - -#endif // __linux__ - -TEST_P(SimpleTcpSocketTest, SetSocketDetachFilterNoInstalledFilter) { - // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. - SKIP_IF(IsRunningOnGvisor()); - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - constexpr int val = 0; - ASSERT_THAT( - setsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(SimpleTcpSocketTest, GetSocketDetachFilter) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len), - SyscallFailsWithErrno(ENOPROTOOPT)); -} - -TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) { - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - constexpr int kLingerTimeout = 10; // Seconds. - - // Set the SO_LINGER option. - struct linger sl = { - .l_onoff = 1, - .l_linger = kLingerTimeout, - }; - ASSERT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), - SyscallSucceeds()); - - struct pollfd poll_fd = { - .fd = s.get(), - .events = POLLHUP, - }; - constexpr int kPollTimeoutMs = 0; - ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), - SyscallSucceedsWithValue(1)); - - auto const start_time = absl::Now(); - EXPECT_THAT(close(s.release()), SyscallSucceeds()); - auto const end_time = absl::Now(); - - // Close() should not linger and return immediately. - ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout)); -} - -// Tests that SO_ACCEPTCONN returns non zero value for listening sockets. -TEST_P(TcpSocketTest, GetSocketAcceptConnListener) { - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT(getsockopt(listener_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceeds()); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 1); -} - -// Tests that SO_ACCEPTCONN returns zero value for not listening sockets. -TEST_P(TcpSocketTest, GetSocketAcceptConnNonListener) { - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT(getsockopt(first_fd, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceeds()); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); - - ASSERT_THAT(getsockopt(second_fd, SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceeds()); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} - -TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) { - // TODO(b/171345701): Fix the TCP state for listening socket on shutdown. - SKIP_IF(IsRunningOnGvisor()); - - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - - // Initialize address to the loopback one. - sockaddr_storage addr = - ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); - socklen_t addrlen = sizeof(addr); - - // Bind to some port then start listening. - ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), - SyscallSucceeds()); - - ASSERT_THAT(listen(s.get(), SOMAXCONN), SyscallSucceeds()); - - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceeds()); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 1); - - EXPECT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds()); - ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length), - SyscallSucceeds()); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, 0); -} - -// Tests that connecting to an unspecified address results in ECONNREFUSED. -TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { - sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - memset(&addr, 0, addrlen); - addr.ss_family = GetParam(); - auto do_connect = [&addr, addrlen]() { - FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( - Socket(addr.ss_family, SOCK_STREAM, IPPROTO_TCP)); - ASSERT_THAT( - RetryEINTR(connect)(s.get(), reinterpret_cast<struct sockaddr*>(&addr), - addrlen), - SyscallFailsWithErrno(ECONNREFUSED)); - }; - do_connect(); - // Test the v4 mapped address as well. - if (GetParam() == AF_INET6) { - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); - sin6->sin6_addr.s6_addr[10] = sin6->sin6_addr.s6_addr[11] = 0xff; - do_connect(); - } -} - -INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, - ::testing::Values(AF_INET, AF_INET6)); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/tgkill.cc b/test/syscalls/linux/tgkill.cc deleted file mode 100644 index 80acae5de..000000000 --- a/test/syscalls/linux/tgkill.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(TgkillTest, InvalidTID) { - EXPECT_THAT(tgkill(getpid(), -1, 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(tgkill(getpid(), 0, 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(TgkillTest, InvalidTGID) { - EXPECT_THAT(tgkill(-1, gettid(), 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(tgkill(0, gettid(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(TgkillTest, ValidInput) { - EXPECT_THAT(tgkill(getpid(), gettid(), 0), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/time.cc b/test/syscalls/linux/time.cc deleted file mode 100644 index e75bba669..000000000 --- a/test/syscalls/linux/time.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <time.h> - -#include "gtest/gtest.h" -#include "test/util/proc_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -constexpr long kFudgeSeconds = 5; - -#if defined(__x86_64__) || defined(__i386__) -// Mimics the time(2) wrapper from glibc prior to 2.15. -time_t vsyscall_time(time_t* t) { - constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400; - return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t); -} - -TEST(TimeTest, VsyscallTime_Succeeds) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - - time_t t1, t2; - - { - const DisableSave ds; // Timing assertions. - EXPECT_THAT(time(&t1), SyscallSucceeds()); - EXPECT_THAT(vsyscall_time(&t2), SyscallSucceeds()); - } - - // Time should be monotonic. - EXPECT_LE(static_cast<long>(t1), static_cast<long>(t2)); - - // Check that it's within kFudge seconds. - EXPECT_LE(static_cast<long>(t2), static_cast<long>(t1) + kFudgeSeconds); - - // Redo with save. - EXPECT_THAT(time(&t1), SyscallSucceeds()); - EXPECT_THAT(vsyscall_time(&t2), SyscallSucceeds()); - - // Time should be monotonic. - EXPECT_LE(static_cast<long>(t1), static_cast<long>(t2)); -} - -TEST(TimeTest, VsyscallTime_InvalidAddressSIGSEGV) { - EXPECT_EXIT(vsyscall_time(reinterpret_cast<time_t*>(0x1)), - ::testing::KilledBySignal(SIGSEGV), ""); -} - -// Mimics the gettimeofday(2) wrapper from the Go runtime <= 1.2. -int vsyscall_gettimeofday(struct timeval* tv, struct timezone* tz) { - constexpr uint64_t kVsyscallGettimeofdayEntry = 0xffffffffff600000; - return reinterpret_cast<int (*)(struct timeval*, struct timezone*)>( - kVsyscallGettimeofdayEntry)(tv, tz); -} - -TEST(TimeTest, VsyscallGettimeofday_Succeeds) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - - struct timeval tv1, tv2; - struct timezone tz1, tz2; - - { - const DisableSave ds; // Timing assertions. - EXPECT_THAT(gettimeofday(&tv1, &tz1), SyscallSucceeds()); - EXPECT_THAT(vsyscall_gettimeofday(&tv2, &tz2), SyscallSucceeds()); - } - - // See above. - EXPECT_LE(static_cast<long>(tv1.tv_sec), static_cast<long>(tv2.tv_sec)); - EXPECT_LE(static_cast<long>(tv2.tv_sec), - static_cast<long>(tv1.tv_sec) + kFudgeSeconds); - - // Redo with save. - EXPECT_THAT(gettimeofday(&tv1, &tz1), SyscallSucceeds()); - EXPECT_THAT(vsyscall_gettimeofday(&tv2, &tz2), SyscallSucceeds()); -} - -TEST(TimeTest, VsyscallGettimeofday_InvalidAddressSIGSEGV) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - - EXPECT_EXIT(vsyscall_gettimeofday(reinterpret_cast<struct timeval*>(0x1), - reinterpret_cast<struct timezone*>(0x1)), - ::testing::KilledBySignal(SIGSEGV), ""); -} -#endif - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/timerfd.cc b/test/syscalls/linux/timerfd.cc deleted file mode 100644 index c4f8fdd7a..000000000 --- a/test/syscalls/linux/timerfd.cc +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <poll.h> -#include <sys/timerfd.h> -#include <time.h> - -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Wrapper around timerfd_create(2) that returns a FileDescriptor. -PosixErrorOr<FileDescriptor> TimerfdCreate(int clockid, int flags) { - int fd = timerfd_create(clockid, flags); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, "timerfd_create failed"); - } - return FileDescriptor(fd); -} - -// In tests that race a timerfd with a sleep, some slack is required because: -// -// - Timerfd expirations are asynchronous with respect to nanosleeps. -// -// - Because clock_gettime(CLOCK_MONOTONIC) is implemented through the VDSO, -// it technically uses a closely-related, but distinct, time domain from the -// CLOCK_MONOTONIC used to trigger timerfd expirations. The same applies to -// CLOCK_BOOTTIME which is an alias for CLOCK_MONOTONIC. -absl::Duration TimerSlack() { return absl::Milliseconds(500); } - -class TimerfdTest : public ::testing::TestWithParam<int> {}; - -TEST_P(TimerfdTest, IsInitiallyStopped) { - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - struct itimerspec its = {}; - ASSERT_THAT(timerfd_gettime(tfd.get(), &its), SyscallSucceeds()); - EXPECT_EQ(0, its.it_value.tv_sec); - EXPECT_EQ(0, its.it_value.tv_nsec); -} - -TEST_P(TimerfdTest, SingleShot) { - constexpr absl::Duration kDelay = absl::Seconds(1); - - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(kDelay); - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - // The timer should fire exactly once since the interval is zero. - absl::SleepFor(kDelay + TimerSlack()); - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - EXPECT_EQ(1, val); -} - -TEST_P(TimerfdTest, Periodic) { - constexpr absl::Duration kDelay = absl::Seconds(1); - constexpr int kPeriods = 3; - - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(kDelay); - its.it_interval = absl::ToTimespec(kDelay); - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - // Expect to see at least kPeriods expirations. More may occur due to the - // timer slack, or due to delays from scheduling or save/restore. - absl::SleepFor(kPeriods * kDelay + TimerSlack()); - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - EXPECT_GE(val, kPeriods); -} - -TEST_P(TimerfdTest, BlockingRead) { - constexpr absl::Duration kDelay = absl::Seconds(3); - - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - struct itimerspec its = {}; - its.it_value.tv_sec = absl::ToInt64Seconds(kDelay); - auto const start_time = absl::Now(); - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - // read should block until the timer fires. - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - auto const end_time = absl::Now(); - EXPECT_EQ(1, val); - EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay); -} - -TEST_P(TimerfdTest, NonblockingRead_NoRandomSave) { - constexpr absl::Duration kDelay = absl::Seconds(5); - - auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); - - // Since the timer is initially disabled and has never fired, read should - // return EAGAIN. - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallFailsWithErrno(EAGAIN)); - - DisableSave ds; // Timing-sensitive. - - // Arm the timer. - struct itimerspec its = {}; - its.it_value.tv_sec = absl::ToInt64Seconds(kDelay); - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - // Since the timer has not yet fired, read should return EAGAIN. - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallFailsWithErrno(EAGAIN)); - - ds.reset(); // No longer timing-sensitive. - - // After the timer fires, read should indicate 1 expiration. - absl::SleepFor(kDelay + TimerSlack()); - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - EXPECT_EQ(1, val); - - // The successful read should have reset the number of expirations. - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) { - constexpr absl::Duration kDelay = absl::Seconds(3); - - auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); - struct itimerspec its = {}; - its.it_value.tv_sec = absl::ToInt64Seconds(kDelay); - auto const start_time = absl::Now(); - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - // poll should block until the timer fires. - struct pollfd pfd = {}; - pfd.fd = tfd.get(); - pfd.events = POLLIN; - ASSERT_THAT(poll(&pfd, /* nfds = */ 1, - /* timeout = */ 2 * absl::ToInt64Seconds(kDelay) * 1000), - SyscallSucceedsWithValue(1)); - auto const end_time = absl::Now(); - EXPECT_EQ(POLLIN, pfd.revents); - EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay); - - // Call timerfd_settime again with a value of 0. This should reset the number - // of expirations to 0, causing read to return EAGAIN since the timerfd is - // non-blocking. - its.it_value.tv_sec = 0; - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(TimerfdTest, SetAbsoluteTime) { - constexpr absl::Duration kDelay = absl::Seconds(3); - - // Use a non-blocking timerfd so that if TFD_TIMER_ABSTIME is incorrectly - // non-functional, we get EAGAIN rather than a test timeout. - auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); - struct itimerspec its = {}; - ASSERT_THAT(clock_gettime(GetParam(), &its.it_value), SyscallSucceeds()); - its.it_value.tv_sec += absl::ToInt64Seconds(kDelay); - ASSERT_THAT(timerfd_settime(tfd.get(), TFD_TIMER_ABSTIME, &its, nullptr), - SyscallSucceeds()); - - absl::SleepFor(kDelay + TimerSlack()); - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - EXPECT_EQ(1, val); -} - -TEST_P(TimerfdTest, IllegalSeek) { - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - if (!IsRunningWithVFS1()) { - EXPECT_THAT(lseek(tfd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE)); - } -} - -TEST_P(TimerfdTest, IllegalPread) { - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - int val; - EXPECT_THAT(pread(tfd.get(), &val, sizeof(val), 0), - SyscallFailsWithErrno(ESPIPE)); -} - -TEST_P(TimerfdTest, IllegalPwrite) { - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); - EXPECT_THAT(pwrite(tfd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE)); - if (!IsRunningWithVFS1()) { - } -} - -TEST_P(TimerfdTest, IllegalWrite) { - auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); - uint64_t val = 0; - EXPECT_THAT(write(tfd.get(), &val, sizeof(val)), - SyscallFailsWithErrno(EINVAL)); -} - -std::string PrintClockId(::testing::TestParamInfo<int> info) { - switch (info.param) { - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - default: - return absl::StrCat(info.param); - } -} - -INSTANTIATE_TEST_SUITE_P(AllTimerTypes, TimerfdTest, - ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME), - PrintClockId); - -TEST(TimerfdClockRealtimeTest, ClockRealtime) { - // Since CLOCK_REALTIME can, by definition, change, we can't make any - // non-flaky assertions about the amount of time it takes for a - // CLOCK_REALTIME-based timer to expire. Just check that it expires at all, - // and hope it happens before the test times out. - constexpr int kDelaySecs = 1; - - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_REALTIME, 0)); - struct itimerspec its = {}; - its.it_value.tv_sec = kDelaySecs; - ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), - SyscallSucceeds()); - - uint64_t val = 0; - ASSERT_THAT(ReadFd(tfd.get(), &val, sizeof(uint64_t)), - SyscallSucceedsWithValue(sizeof(uint64_t))); - EXPECT_EQ(1, val); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc deleted file mode 100644 index 93a98adb1..000000000 --- a/test/syscalls/linux/timers.cc +++ /dev/null @@ -1,565 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <signal.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <syscall.h> -#include <time.h> -#include <unistd.h> - -#include <atomic> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/timer_util.h" - -ABSL_FLAG(bool, timers_test_sleep, false, - "If true, sleep forever instead of running tests."); - -using ::testing::_; -using ::testing::AnyOf; - -namespace gvisor { -namespace testing { -namespace { - -#ifndef CPUCLOCK_PROF -#define CPUCLOCK_PROF 0 -#endif // CPUCLOCK_PROF - -PosixErrorOr<absl::Duration> ProcessCPUTime(pid_t pid) { - // Use pid-specific CPUCLOCK_PROF, which is the clock used to enforce - // RLIMIT_CPU. - clockid_t clockid = (~static_cast<clockid_t>(pid) << 3) | CPUCLOCK_PROF; - - struct timespec ts; - int ret = clock_gettime(clockid, &ts); - if (ret < 0) { - return PosixError(errno, "clock_gettime failed"); - } - - return absl::DurationFromTimespec(ts); -} - -void NoopSignalHandler(int signo) { - TEST_CHECK_MSG(SIGXCPU == signo, - "NoopSigHandler did not receive expected signal"); -} - -void UninstallingSignalHandler(int signo) { - TEST_CHECK_MSG(SIGXCPU == signo, - "UninstallingSignalHandler did not receive expected signal"); - struct sigaction rev_action; - rev_action.sa_handler = SIG_DFL; - rev_action.sa_flags = 0; - sigemptyset(&rev_action.sa_mask); - sigaction(SIGXCPU, &rev_action, nullptr); -} - -TEST(TimerTest, ProcessKilledOnCPUSoftLimit) { - constexpr absl::Duration kSoftLimit = absl::Seconds(1); - constexpr absl::Duration kHardLimit = absl::Seconds(3); - - struct rlimit cpu_limits; - cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); - cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); - - int pid = fork(); - MaybeSave(); - if (pid == 0) { - TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); - MaybeSave(); - for (;;) { - } - } - ASSERT_THAT(pid, SyscallSucceeds()); - auto c = Cleanup([pid] { - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(WTERMSIG(status), SIGXCPU); - }); - - // Wait for the child to exit, but do not reap it. This will allow us to check - // its CPU usage while it is zombied. - EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), - SyscallSucceeds()); - - // Assert that the child spent 1s of CPU before getting killed. - // - // We must be careful to use CPUCLOCK_PROF, the same clock used for RLIMIT_CPU - // enforcement, to get correct results. Note that this is slightly different - // from rusage-reported CPU usage: - // - // RLIMIT_CPU, CPUCLOCK_PROF use kernel/sched/cputime.c:thread_group_cputime. - // rusage uses kernel/sched/cputime.c:thread_group_cputime_adjusted. - absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); - EXPECT_GE(cpu, kSoftLimit); - - // Child did not make it to the hard limit. - // - // Linux sends SIGXCPU synchronously with CPU tick updates. See - // kernel/time/timer.c:update_process_times: - // => account_process_tick // update task CPU usage. - // => run_posix_cpu_timers // enforce RLIMIT_CPU, sending signal. - // - // Thus, only chance for this to flake is if the system time required to - // deliver the signal exceeds 2s. - EXPECT_LT(cpu, kHardLimit); -} - -TEST(TimerTest, ProcessPingedRepeatedlyAfterCPUSoftLimit) { - struct sigaction new_action; - new_action.sa_handler = UninstallingSignalHandler; - new_action.sa_flags = 0; - sigemptyset(&new_action.sa_mask); - - constexpr absl::Duration kSoftLimit = absl::Seconds(1); - constexpr absl::Duration kHardLimit = absl::Seconds(10); - - struct rlimit cpu_limits; - cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); - cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); - - int pid = fork(); - MaybeSave(); - if (pid == 0) { - TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0); - MaybeSave(); - TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); - MaybeSave(); - for (;;) { - } - } - ASSERT_THAT(pid, SyscallSucceeds()); - auto c = Cleanup([pid] { - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(WTERMSIG(status), SIGXCPU); - }); - - // Wait for the child to exit, but do not reap it. This will allow us to check - // its CPU usage while it is zombied. - EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), - SyscallSucceeds()); - - absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); - // Following signals come every CPU second. - EXPECT_GE(cpu, kSoftLimit + absl::Seconds(1)); - - // Child did not make it to the hard limit. - // - // As above, should not flake. - EXPECT_LT(cpu, kHardLimit); -} - -TEST(TimerTest, ProcessKilledOnCPUHardLimit) { - struct sigaction new_action; - new_action.sa_handler = NoopSignalHandler; - new_action.sa_flags = 0; - sigemptyset(&new_action.sa_mask); - - constexpr absl::Duration kSoftLimit = absl::Seconds(1); - constexpr absl::Duration kHardLimit = absl::Seconds(3); - - struct rlimit cpu_limits; - cpu_limits.rlim_cur = absl::ToInt64Seconds(kSoftLimit); - cpu_limits.rlim_max = absl::ToInt64Seconds(kHardLimit); - - int pid = fork(); - MaybeSave(); - if (pid == 0) { - TEST_PCHECK(sigaction(SIGXCPU, &new_action, nullptr) == 0); - MaybeSave(); - TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0); - MaybeSave(); - for (;;) { - } - } - ASSERT_THAT(pid, SyscallSucceeds()); - auto c = Cleanup([pid] { - int status; - EXPECT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(WTERMSIG(status), SIGKILL); - }); - - // Wait for the child to exit, but do not reap it. This will allow us to check - // its CPU usage while it is zombied. - EXPECT_THAT(waitid(P_PID, pid, nullptr, WEXITED | WNOWAIT), - SyscallSucceeds()); - - absl::Duration cpu = ASSERT_NO_ERRNO_AND_VALUE(ProcessCPUTime(pid)); - EXPECT_GE(cpu, kHardLimit); -} - -// See timerfd.cc:TimerSlack() for rationale. -constexpr absl::Duration kTimerSlack = absl::Milliseconds(500); - -TEST(IntervalTimerTest, IsInitiallyStopped) { - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_NONE; - const auto timer = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - const struct itimerspec its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); - EXPECT_EQ(0, its.it_value.tv_sec); - EXPECT_EQ(0, its.it_value.tv_nsec); -} - -// Kernel can create multiple timers without issue. -// -// Regression test for gvisor.dev/issue/1738. -TEST(IntervalTimerTest, MultipleTimers) { - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_NONE; - const auto timer1 = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - const auto timer2 = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); -} - -TEST(IntervalTimerTest, SingleShotSilent) { - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_NONE; - const auto timer = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kDelay = absl::Seconds(1); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(kDelay); - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // The timer should count down to 0 and stop since the interval is zero. No - // overruns should be counted. - absl::SleepFor(kDelay + kTimerSlack); - its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); - EXPECT_EQ(0, its.it_value.tv_sec); - EXPECT_EQ(0, its.it_value.tv_nsec); - EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0)); -} - -TEST(IntervalTimerTest, PeriodicSilent) { - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_NONE; - const auto timer = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - - absl::SleepFor(kPeriod * 3 + kTimerSlack); - - // The timer should still be running. - its = ASSERT_NO_ERRNO_AND_VALUE(timer.Get()); - EXPECT_TRUE(its.it_value.tv_nsec != 0 || its.it_value.tv_sec != 0); - - // Timer expirations are not counted as overruns under SIGEV_NONE. - EXPECT_THAT(timer.Overruns(), IsPosixErrorOkAndHolds(0)); -} - -std::atomic<int> counted_signals; - -void IntervalTimerCountingSignalHandler(int sig, siginfo_t* info, - void* ucontext) { - counted_signals.fetch_add(1 + info->si_overrun); -} - -TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) { - constexpr int kSigno = SIGUSR1; - constexpr int kSigvalue = 42; - - // Install our signal handler. - counted_signals.store(0); - struct sigaction sa = {}; - sa.sa_sigaction = IntervalTimerCountingSignalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - const auto scoped_sigaction = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); - - // Ensure that kSigno is unblocked on at least one thread. - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, kSigno)); - - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_SIGNAL; - sev.sigev_signo = kSigno; - sev.sigev_value.sival_int = kSigvalue; - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - constexpr int kCycles = 3; - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - - absl::SleepFor(kPeriod * kCycles + kTimerSlack); - EXPECT_GE(counted_signals.load(), kCycles); -} - -TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) { - constexpr int kSigno = SIGUSR1; - constexpr int kSigvalue = 42; - - // Block kSigno so that we can accumulate overruns. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); - - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = kSigno; - sev.sigev_value.sival_int = kSigvalue; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - constexpr int kCycles = 3; - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - absl::SleepFor(kPeriod * kCycles + kTimerSlack); - - // At least kCycles expirations should have occurred, resulting in kCycles-1 - // overruns (the first expiration sent the signal successfully). - siginfo_t si; - struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); - ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallSucceedsWithValue(kSigno)); - EXPECT_EQ(si.si_signo, kSigno); - EXPECT_EQ(si.si_code, SI_TIMER); - EXPECT_EQ(si.si_timerid, timer.get()); - EXPECT_GE(si.si_overrun, kCycles - 1); - EXPECT_EQ(si.si_int, kSigvalue); - - // Kill the timer, then drain any additional signal it may have enqueued. We - // can't do this before the preceding sigtimedwait because stopping or - // deleting the timer resets si_overrun to 0. - timer.reset(); - sigtimedwait(&mask, &si, &zero_ts); -} - -TEST(IntervalTimerTest, OtherThreadGroup) { - constexpr int kSigno = SIGUSR1; - - // Create a subprocess that does nothing until killed. - pid_t child_pid; - const auto sp = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( - "/proc/self/exe", ExecveArray({"timers", "--timers_test_sleep"}), - ExecveArray(), &child_pid, nullptr)); - - // Verify that we can't create a timer that would send signals to it. - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = kSigno; - sev.sigev_notify_thread_id = child_pid; - EXPECT_THAT(TimerCreate(CLOCK_MONOTONIC, sev), PosixErrorIs(EINVAL, _)); -} - -TEST(IntervalTimerTest, RealTimeSignalsAreNotDuplicated) { - const int kSigno = SIGRTMIN; - constexpr int kSigvalue = 42; - - // Block signo so that we can accumulate overruns. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - const auto scoped_sigmask = ScopedSignalMask(SIG_BLOCK, mask); - - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = kSigno; - sev.sigev_value.sival_int = kSigvalue; - sev.sigev_notify_thread_id = gettid(); - const auto timer = - ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - constexpr int kCycles = 3; - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - absl::SleepFor(kPeriod * kCycles + kTimerSlack); - - // Stop the timer so that no further signals are enqueued after sigtimedwait. - struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); - its.it_value = its.it_interval = zero_ts; - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // The timer should have sent only a single signal, even though the kernel - // supports enqueueing of multiple RT signals. - siginfo_t si; - ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallSucceedsWithValue(kSigno)); - EXPECT_EQ(si.si_signo, kSigno); - EXPECT_EQ(si.si_code, SI_TIMER); - EXPECT_EQ(si.si_timerid, timer.get()); - // si_overrun was reset by timer_settime. - EXPECT_EQ(si.si_overrun, 0); - EXPECT_EQ(si.si_int, kSigvalue); - EXPECT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST(IntervalTimerTest, AlreadyPendingSignal) { - constexpr int kSigno = SIGUSR1; - constexpr int kSigvalue = 42; - - // Block kSigno so that we can accumulate overruns. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - const auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); - - // Send ourselves a signal, preventing the timer from enqueuing. - ASSERT_THAT(tgkill(getpid(), gettid(), kSigno), SyscallSucceeds()); - - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = kSigno; - sev.sigev_value.sival_int = kSigvalue; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - constexpr int kCycles = 3; - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // End the sleep one cycle short; we will sleep for one more cycle below. - absl::SleepFor(kPeriod * (kCycles - 1)); - - // Dequeue the first signal, which we sent to ourselves with tgkill. - siginfo_t si; - struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); - ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallSucceedsWithValue(kSigno)); - EXPECT_EQ(si.si_signo, kSigno); - // glibc sigtimedwait silently replaces SI_TKILL with SI_USER: - // sysdeps/unix/sysv/linux/sigtimedwait.c:__sigtimedwait(). This isn't - // documented, so we don't depend on it. - EXPECT_THAT(si.si_code, AnyOf(SI_USER, SI_TKILL)); - - // Sleep for 1 more cycle to give the timer time to send a signal. - absl::SleepFor(kPeriod + kTimerSlack); - - // At least kCycles expirations should have occurred, resulting in kCycles-1 - // overruns (the last expiration sent the signal successfully). - ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallSucceedsWithValue(kSigno)); - EXPECT_EQ(si.si_signo, kSigno); - EXPECT_EQ(si.si_code, SI_TIMER); - EXPECT_EQ(si.si_timerid, timer.get()); - EXPECT_GE(si.si_overrun, kCycles - 1); - EXPECT_EQ(si.si_int, kSigvalue); - - // Kill the timer, then drain any additional signal it may have enqueued. We - // can't do this before the preceding sigtimedwait because stopping or - // deleting the timer resets si_overrun to 0. - timer.reset(); - sigtimedwait(&mask, &si, &zero_ts); -} - -TEST(IntervalTimerTest, IgnoredSignalCountsAsOverrun) { - constexpr int kSigno = SIGUSR1; - constexpr int kSigvalue = 42; - - // Ignore kSigno. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - const auto scoped_sigaction = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(kSigno, sa)); - - // Unblock kSigno so that ignored signals will be discarded. - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, kSigno); - auto scoped_sigmask = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, mask)); - - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = kSigno; - sev.sigev_value.sival_int = kSigvalue; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - - constexpr absl::Duration kPeriod = absl::Seconds(1); - constexpr int kCycles = 3; - struct itimerspec its = {}; - its.it_value = its.it_interval = absl::ToTimespec(kPeriod); - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // End the sleep one cycle short; we will sleep for one more cycle below. - absl::SleepFor(kPeriod * (kCycles - 1)); - - // Block kSigno so that ignored signals will be enqueued. - scoped_sigmask.Release()(); - scoped_sigmask = ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_BLOCK, mask)); - - // Sleep for 1 more cycle to give the timer time to send a signal. - absl::SleepFor(kPeriod + kTimerSlack); - - // At least kCycles expirations should have occurred, resulting in kCycles-1 - // overruns (the last expiration sent the signal successfully). - siginfo_t si; - struct timespec zero_ts = absl::ToTimespec(absl::ZeroDuration()); - ASSERT_THAT(sigtimedwait(&mask, &si, &zero_ts), - SyscallSucceedsWithValue(kSigno)); - EXPECT_EQ(si.si_signo, kSigno); - EXPECT_EQ(si.si_code, SI_TIMER); - EXPECT_EQ(si.si_timerid, timer.get()); - EXPECT_GE(si.si_overrun, kCycles - 1); - EXPECT_EQ(si.si_int, kSigvalue); - - // Kill the timer, then drain any additional signal it may have enqueued. We - // can't do this before the preceding sigtimedwait because stopping or - // deleting the timer resets si_overrun to 0. - timer.reset(); - sigtimedwait(&mask, &si, &zero_ts); -} - -} // namespace -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_timers_test_sleep)) { - while (true) { - absl::SleepFor(absl::Seconds(10)); - } - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/tkill.cc b/test/syscalls/linux/tkill.cc deleted file mode 100644 index 8d8ebbb24..000000000 --- a/test/syscalls/linux/tkill.cc +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -#include <cerrno> -#include <csignal> - -#include "gtest/gtest.h" -#include "test/util/logging.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -static int tkill(pid_t tid, int sig) { - int ret; - do { - // NOTE(b/25434735): tkill(2) could return EAGAIN for RT signals. - ret = syscall(SYS_tkill, tid, sig); - } while (ret == -1 && errno == EAGAIN); - return ret; -} - -TEST(TkillTest, InvalidTID) { - EXPECT_THAT(tkill(-1, 0), SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(tkill(0, 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(TkillTest, ValidTID) { - EXPECT_THAT(tkill(gettid(), 0), SyscallSucceeds()); -} - -void SigHandler(int sig, siginfo_t* info, void* context) { - TEST_CHECK(sig == SIGRTMAX); - TEST_CHECK(info->si_pid == getpid()); - TEST_CHECK(info->si_uid == getuid()); - TEST_CHECK(info->si_code == SI_TKILL); -} - -// Test with a real signal. Regression test for b/24790092. -TEST(TkillTest, ValidTIDAndRealSignal) { - struct sigaction sa; - sa.sa_sigaction = SigHandler; - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - ASSERT_THAT(sigaction(SIGRTMAX, &sa, nullptr), SyscallSucceeds()); - // InitGoogle blocks all RT signals, so we need undo it. - sigset_t unblock; - sigemptyset(&unblock); - sigaddset(&unblock, SIGRTMAX); - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &unblock, nullptr), SyscallSucceeds()); - EXPECT_THAT(tkill(gettid(), SIGRTMAX), SyscallSucceeds()); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc deleted file mode 100644 index 17832c47d..000000000 --- a/test/syscalls/linux/truncate.cc +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <signal.h> -#include <sys/resource.h> -#include <sys/stat.h> -#include <sys/vfs.h> -#include <time.h> -#include <unistd.h> - -#include <iostream> -#include <string> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class FixtureTruncateTest : public FileTest { - void SetUp() override { FileTest::SetUp(); } -}; - -TEST_F(FixtureTruncateTest, Truncate) { - // Get the current rlimit and restore after test run. - struct rlimit initial_lim; - ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - auto cleanup = Cleanup([&initial_lim] { - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - }); - - // Check that it starts at size zero. - struct stat buf; - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); - - // Stay at size zero. - EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); - - // Grow to ten bytes. - EXPECT_THAT(truncate(test_file_name_.c_str(), 10), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 10); - - // Can't be truncated to a negative number. - EXPECT_THAT(truncate(test_file_name_.c_str(), -1), - SyscallFailsWithErrno(EINVAL)); - - // Try growing past the file size limit. - sigset_t new_mask; - sigemptyset(&new_mask); - sigaddset(&new_mask, SIGXFSZ); - sigprocmask(SIG_BLOCK, &new_mask, nullptr); - struct timespec timelimit; - timelimit.tv_sec = 10; - timelimit.tv_nsec = 0; - - struct rlimit setlim; - setlim.rlim_cur = 1024; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds()); - EXPECT_THAT(truncate(test_file_name_.c_str(), 1025), - SyscallFailsWithErrno(EFBIG)); - EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ); - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds()); - - // Shrink back down to zero. - EXPECT_THAT(truncate(test_file_name_.c_str(), 0), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); -} - -TEST_F(FixtureTruncateTest, Ftruncate) { - // Get the current rlimit and restore after test run. - struct rlimit initial_lim; - ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - auto cleanup = Cleanup([&initial_lim] { - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - }); - - // Check that it starts at size zero. - struct stat buf; - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); - - // Stay at size zero. - EXPECT_THAT(ftruncate(test_file_fd_.get(), 0), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); - - // Grow to ten bytes. - EXPECT_THAT(ftruncate(test_file_fd_.get(), 10), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 10); - - // Can't be truncated to a negative number. - EXPECT_THAT(ftruncate(test_file_fd_.get(), -1), - SyscallFailsWithErrno(EINVAL)); - - // Try growing past the file size limit. - sigset_t new_mask; - sigemptyset(&new_mask); - sigaddset(&new_mask, SIGXFSZ); - sigprocmask(SIG_BLOCK, &new_mask, nullptr); - struct timespec timelimit; - timelimit.tv_sec = 10; - timelimit.tv_nsec = 0; - - struct rlimit setlim; - setlim.rlim_cur = 1024; - setlim.rlim_max = RLIM_INFINITY; - ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds()); - EXPECT_THAT(ftruncate(test_file_fd_.get(), 1025), - SyscallFailsWithErrno(EFBIG)); - EXPECT_EQ(sigtimedwait(&new_mask, nullptr, &timelimit), SIGXFSZ); - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds()); - - // Shrink back down to zero. - EXPECT_THAT(ftruncate(test_file_fd_.get(), 0), SyscallSucceeds()); - ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds()); - EXPECT_EQ(buf.st_size, 0); -} - -// Truncating a file down clears that portion of the file. -TEST_F(FixtureTruncateTest, FtruncateShrinkGrow) { - std::vector<char> buf(10, 'a'); - EXPECT_THAT(WriteFd(test_file_fd_.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(buf.size())); - - // Shrink then regrow the file. This should clear the second half of the file. - EXPECT_THAT(ftruncate(test_file_fd_.get(), 5), SyscallSucceeds()); - EXPECT_THAT(ftruncate(test_file_fd_.get(), 10), SyscallSucceeds()); - - EXPECT_THAT(lseek(test_file_fd_.get(), 0, SEEK_SET), SyscallSucceeds()); - - std::vector<char> buf2(10); - EXPECT_THAT(ReadFd(test_file_fd_.get(), buf2.data(), buf2.size()), - SyscallSucceedsWithValue(buf2.size())); - - std::vector<char> expect = {'a', 'a', 'a', 'a', 'a', - '\0', '\0', '\0', '\0', '\0'}; - EXPECT_EQ(expect, buf2); -} - -TEST(TruncateTest, TruncateDir) { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(truncate(temp_dir.path().c_str(), 0), - SyscallFailsWithErrno(EISDIR)); -} - -TEST(TruncateTest, FtruncateDir) { - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_dir.path(), O_DIRECTORY | O_RDONLY)); - EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(TruncateTest, TruncateNonWriteable) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // always override write permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */)); - EXPECT_THAT(truncate(temp_file.path().c_str(), 0), - SyscallFailsWithErrno(EACCES)); -} - -TEST(TruncateTest, FtruncateNonWriteable) { - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); - EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); -} - -TEST(TruncateTest, FtruncateWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */)); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); - EXPECT_THAT(ftruncate(fd.get(), 0), AnyOf(SyscallFailsWithErrno(EBADF), - SyscallFailsWithErrno(EINVAL))); -} - -// ftruncate(2) should succeed as long as the file descriptor is writeable, -// regardless of whether the file permissions allow writing. -TEST(TruncateTest, FtruncateWithoutWritePermission_NoRandomSave) { - // Drop capabilities that allow us to override file permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - - // The only time we can open a file with flags forbidden by its permissions - // is when we are creating the file. We cannot re-open with the same flags, - // so we cannot restore an fd obtained from such an operation. - const DisableSave ds; - auto path = NewTempAbsPath(); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDWR | O_CREAT, 0444)); - - // In goferfs, ftruncate may be converted to a remote truncate operation that - // unavoidably requires write permission. - SKIP_IF(IsRunningOnGvisor() && !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(path))); - ASSERT_THAT(ftruncate(fd.get(), 100), SyscallSucceeds()); -} - -TEST(TruncateTest, TruncateNonExist) { - EXPECT_THAT(truncate("/foo/bar", 0), SyscallFailsWithErrno(ENOENT)); -} - -TEST(TruncateTest, FtruncateVirtualTmp_NoRandomSave) { - auto temp_file = NewTempAbsPathInDir("/dev/shm"); - const DisableSave ds; // Incompatible permissions. - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file, O_RDWR | O_CREAT | O_EXCL, 0)); - EXPECT_THAT(ftruncate(fd.get(), 100), SyscallSucceeds()); -} - -// NOTE: There are additional truncate(2)/ftruncate(2) tests in mknod.cc -// which are there to avoid running the tests on a number of different -// filesystems which may not support mknod. - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc deleted file mode 100644 index 13ed0d68a..000000000 --- a/test/syscalls/linux/tuntap.cc +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/if_arp.h> -#include <linux/if_ether.h> -#include <linux/if_tun.h> -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/ascii.h" -#include "absl/strings/str_split.h" -#include "test/syscalls/linux/socket_netlink_route_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { -namespace { - -constexpr int kIPLen = 4; - -constexpr const char kDevNetTun[] = "/dev/net/tun"; -constexpr const char kTapName[] = "tap0"; - -#define kTapIPAddr htonl(0x0a000001) /* Inet 10.0.0.1 */ -#define kTapPeerIPAddr htonl(0x0a000002) /* Inet 10.0.0.2 */ - -constexpr const uint8_t kMacA[ETH_ALEN] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; -constexpr const uint8_t kMacB[ETH_ALEN] = {0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}; - -PosixErrorOr<std::set<std::string>> DumpLinkNames() { - ASSIGN_OR_RETURN_ERRNO(auto links, DumpLinks()); - std::set<std::string> names; - for (const auto& link : links) { - names.emplace(link.name); - } - return names; -} - -PosixErrorOr<Link> GetLinkByName(const std::string& name) { - ASSIGN_OR_RETURN_ERRNO(auto links, DumpLinks()); - for (const auto& link : links) { - if (link.name == name) { - return link; - } - } - return PosixError(ENOENT, "interface not found"); -} - -struct pihdr { - uint16_t pi_flags; - uint16_t pi_protocol; -} __attribute__((packed)); - -struct ping_pkt { - pihdr pi; - struct ethhdr eth; - struct iphdr ip; - struct icmphdr icmp; - char payload[64]; -} __attribute__((packed)); - -ping_pkt CreatePingPacket(const uint8_t srcmac[ETH_ALEN], const in_addr_t srcip, - const uint8_t dstmac[ETH_ALEN], - const in_addr_t dstip) { - ping_pkt pkt = {}; - - pkt.pi.pi_protocol = htons(ETH_P_IP); - - memcpy(pkt.eth.h_dest, dstmac, sizeof(pkt.eth.h_dest)); - memcpy(pkt.eth.h_source, srcmac, sizeof(pkt.eth.h_source)); - pkt.eth.h_proto = htons(ETH_P_IP); - - pkt.ip.ihl = 5; - pkt.ip.version = 4; - pkt.ip.tos = 0; - pkt.ip.tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr) + - sizeof(pkt.payload)); - pkt.ip.id = 1; - pkt.ip.frag_off = 1 << 6; // Do not fragment - pkt.ip.ttl = 64; - pkt.ip.protocol = IPPROTO_ICMP; - pkt.ip.daddr = dstip; - pkt.ip.saddr = srcip; - pkt.ip.check = IPChecksum(pkt.ip); - - pkt.icmp.type = ICMP_ECHO; - pkt.icmp.code = 0; - pkt.icmp.checksum = 0; - pkt.icmp.un.echo.sequence = 1; - pkt.icmp.un.echo.id = 1; - - strncpy(pkt.payload, "abcd", sizeof(pkt.payload)); - pkt.icmp.checksum = ICMPChecksum(pkt.icmp, pkt.payload, sizeof(pkt.payload)); - - return pkt; -} - -struct arp_pkt { - pihdr pi; - struct ethhdr eth; - struct arphdr arp; - uint8_t arp_sha[ETH_ALEN]; - uint8_t arp_spa[kIPLen]; - uint8_t arp_tha[ETH_ALEN]; - uint8_t arp_tpa[kIPLen]; -} __attribute__((packed)); - -std::string CreateArpPacket(const uint8_t srcmac[ETH_ALEN], - const in_addr_t srcip, - const uint8_t dstmac[ETH_ALEN], - const in_addr_t dstip) { - std::string buffer; - buffer.resize(sizeof(arp_pkt)); - - arp_pkt* pkt = reinterpret_cast<arp_pkt*>(&buffer[0]); - { - pkt->pi.pi_protocol = htons(ETH_P_ARP); - - memcpy(pkt->eth.h_dest, kMacA, sizeof(pkt->eth.h_dest)); - memcpy(pkt->eth.h_source, kMacB, sizeof(pkt->eth.h_source)); - pkt->eth.h_proto = htons(ETH_P_ARP); - - pkt->arp.ar_hrd = htons(ARPHRD_ETHER); - pkt->arp.ar_pro = htons(ETH_P_IP); - pkt->arp.ar_hln = ETH_ALEN; - pkt->arp.ar_pln = kIPLen; - pkt->arp.ar_op = htons(ARPOP_REPLY); - - memcpy(pkt->arp_sha, srcmac, sizeof(pkt->arp_sha)); - memcpy(pkt->arp_spa, &srcip, sizeof(pkt->arp_spa)); - memcpy(pkt->arp_tha, dstmac, sizeof(pkt->arp_tha)); - memcpy(pkt->arp_tpa, &dstip, sizeof(pkt->arp_tpa)); - } - return buffer; -} - -} // namespace - -TEST(TuntapStaticTest, NetTunExists) { - struct stat statbuf; - ASSERT_THAT(stat(kDevNetTun, &statbuf), SyscallSucceeds()); - // Check that it's a character device with rw-rw-rw- permissions. - EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666); -} - -class TuntapTest : public ::testing::Test { - protected: - void SetUp() override { - have_net_admin_cap_ = - ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)); - - if (have_net_admin_cap_ && !IsRunningOnGvisor()) { - // gVisor always creates enabled/up'd interfaces, while Linux does not (as - // observed in b/110961832). Some of the tests require the Linux stack to - // notify the socket of any link-address-resolution failures. Those - // notifications do not seem to show up when the loopback interface in the - // namespace is down. - auto link = ASSERT_NO_ERRNO_AND_VALUE(GetLinkByName("lo")); - ASSERT_NO_ERRNO(LinkChangeFlags(link.index, IFF_UP, IFF_UP)); - } - } - - void TearDown() override { - if (have_net_admin_cap_) { - // Bring back capability if we had dropped it in test case. - ASSERT_NO_ERRNO(SetCapability(CAP_NET_ADMIN, true)); - } - } - - bool have_net_admin_cap_; -}; - -TEST_F(TuntapTest, CreateInterfaceNoCap) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - ASSERT_NO_ERRNO(SetCapability(CAP_NET_ADMIN, false)); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); - - struct ifreq ifr = {}; - ifr.ifr_flags = IFF_TAP; - strncpy(ifr.ifr_name, kTapName, IFNAMSIZ); - - EXPECT_THAT(ioctl(fd.get(), TUNSETIFF, &ifr), SyscallFailsWithErrno(EPERM)); -} - -TEST_F(TuntapTest, CreateFixedNameInterface) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); - - struct ifreq ifr_set = {}; - ifr_set.ifr_flags = IFF_TAP; - strncpy(ifr_set.ifr_name, kTapName, IFNAMSIZ); - EXPECT_THAT(ioctl(fd.get(), TUNSETIFF, &ifr_set), - SyscallSucceedsWithValue(0)); - - struct ifreq ifr_get = {}; - EXPECT_THAT(ioctl(fd.get(), TUNGETIFF, &ifr_get), - SyscallSucceedsWithValue(0)); - - struct ifreq ifr_expect = ifr_set; - // See __tun_chr_ioctl() in net/drivers/tun.c. - ifr_expect.ifr_flags |= IFF_NOFILTER; - - EXPECT_THAT(DumpLinkNames(), - IsPosixErrorOkAndHolds(::testing::Contains(kTapName))); - EXPECT_THAT(memcmp(&ifr_expect, &ifr_get, sizeof(ifr_get)), ::testing::Eq(0)); -} - -TEST_F(TuntapTest, CreateInterface) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); - - struct ifreq ifr = {}; - ifr.ifr_flags = IFF_TAP; - // Empty ifr.ifr_name. Let kernel assign. - - EXPECT_THAT(ioctl(fd.get(), TUNSETIFF, &ifr), SyscallSucceedsWithValue(0)); - - struct ifreq ifr_get = {}; - EXPECT_THAT(ioctl(fd.get(), TUNGETIFF, &ifr_get), - SyscallSucceedsWithValue(0)); - - std::string ifname = ifr_get.ifr_name; - EXPECT_THAT(ifname, ::testing::StartsWith("tap")); - EXPECT_THAT(DumpLinkNames(), - IsPosixErrorOkAndHolds(::testing::Contains(ifname))); -} - -TEST_F(TuntapTest, InvalidReadWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); - - char buf[128] = {}; - EXPECT_THAT(read(fd.get(), buf, sizeof(buf)), SyscallFailsWithErrno(EBADFD)); - EXPECT_THAT(write(fd.get(), buf, sizeof(buf)), SyscallFailsWithErrno(EBADFD)); -} - -TEST_F(TuntapTest, WriteToDownDevice) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - // FIXME(b/110961832): gVisor always creates enabled/up'd interfaces. - SKIP_IF(IsRunningOnGvisor()); - - FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(kDevNetTun, O_RDWR)); - - // Device created should be down by default. - struct ifreq ifr = {}; - ifr.ifr_flags = IFF_TAP; - EXPECT_THAT(ioctl(fd.get(), TUNSETIFF, &ifr), SyscallSucceedsWithValue(0)); - - char buf[128] = {}; - EXPECT_THAT(write(fd.get(), buf, sizeof(buf)), SyscallFailsWithErrno(EIO)); -} - -PosixErrorOr<FileDescriptor> OpenAndAttachTap(const std::string& dev_name, - const in_addr_t dev_addr) { - // Interface creation. - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(kDevNetTun, O_RDWR)); - - struct ifreq ifr_set = {}; - ifr_set.ifr_flags = IFF_TAP; - strncpy(ifr_set.ifr_name, dev_name.c_str(), IFNAMSIZ); - if (ioctl(fd.get(), TUNSETIFF, &ifr_set) < 0) { - return PosixError(errno); - } - - ASSIGN_OR_RETURN_ERRNO(auto link, GetLinkByName(dev_name)); - - const struct in_addr dev_ipv4_addr = {.s_addr = dev_addr}; - // Interface setup. - EXPECT_NO_ERRNO(LinkAddLocalAddr(link.index, AF_INET, /*prefixlen=*/24, - &dev_ipv4_addr, sizeof(dev_ipv4_addr))); - - if (!IsRunningOnGvisor()) { - // FIXME(b/110961832): gVisor doesn't support setting MAC address on - // interfaces yet. - RETURN_IF_ERRNO(LinkSetMacAddr(link.index, kMacA, sizeof(kMacA))); - - // FIXME(b/110961832): gVisor always creates enabled/up'd interfaces. - RETURN_IF_ERRNO(LinkChangeFlags(link.index, IFF_UP, IFF_UP)); - } - - return fd; -} - -// This test sets up a TAP device and pings kernel by sending ICMP echo request. -// -// It works as the following: -// * Open /dev/net/tun, and create kTapName interface. -// * Use rtnetlink to do initial setup of the interface: -// * Assign IP address 10.0.0.1/24 to kernel. -// * MAC address: kMacA -// * Bring up the interface. -// * Send an ICMP echo reqest (ping) packet from 10.0.0.2 (kMacB) to kernel. -// * Loop to receive packets from TAP device/fd: -// * If packet is an ICMP echo reply, it stops and passes the test. -// * If packet is an ARP request, it responds with canned reply and resends -// the -// ICMP request packet. -TEST_F(TuntapTest, PingKernel) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); - ping_pkt ping_req = - CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr); - std::string arp_rep = - CreateArpPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr); - - // Send ping, this would trigger an ARP request on Linux. - EXPECT_THAT(write(fd.get(), &ping_req, sizeof(ping_req)), - SyscallSucceedsWithValue(sizeof(ping_req))); - - // Receive loop to process inbound packets. - struct inpkt { - union { - pihdr pi; - ping_pkt ping; - arp_pkt arp; - }; - }; - while (1) { - inpkt r = {}; - 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 - << " len: " << n << std::endl; - continue; - } - - // Process ARP packet. - if (n >= sizeof(arp_pkt) && r.pi.pi_protocol == htons(ETH_P_ARP)) { - // Respond with canned ARP reply. - EXPECT_THAT(write(fd.get(), arp_rep.data(), arp_rep.size()), - SyscallSucceedsWithValue(arp_rep.size())); - // First ping request might have been dropped due to mac address not in - // ARP cache. Send it again. - EXPECT_THAT(write(fd.get(), &ping_req, sizeof(ping_req)), - SyscallSucceedsWithValue(sizeof(ping_req))); - } - - // Process ping response packet. - if (n >= sizeof(ping_pkt) && r.pi.pi_protocol == ping_req.pi.pi_protocol && - r.ping.ip.protocol == ping_req.ip.protocol && - !memcmp(&r.ping.ip.saddr, &ping_req.ip.daddr, kIPLen) && - !memcmp(&r.ping.ip.daddr, &ping_req.ip.saddr, kIPLen) && - r.ping.icmp.type == 0 && r.ping.icmp.code == 0) { - // Ends and passes the test. - break; - } - } -} - -TEST_F(TuntapTest, SendUdpTriggersArpResolution) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); - - // Send a UDP packet to remote. - int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - ASSERT_THAT(sock, SyscallSucceeds()); - - struct sockaddr_in remote = { - .sin_family = AF_INET, - .sin_port = htons(42), - .sin_addr = {.s_addr = kTapPeerIPAddr}, - }; - ASSERT_THAT(sendto(sock, "hello", 5, 0, reinterpret_cast<sockaddr*>(&remote), - sizeof(remote)), - SyscallSucceeds()); - - struct inpkt { - union { - pihdr pi; - arp_pkt arp; - }; - }; - while (1) { - inpkt r = {}; - 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 - << " len: " << n << std::endl; - continue; - } - - if (n >= sizeof(arp_pkt) && r.pi.pi_protocol == htons(ETH_P_ARP)) { - break; - } - } -} - -// TCPBlockingConnectFailsArpResolution tests for TCP connect to fail on link -// address resolution failure to a routable, but non existent peer. -TEST_F(TuntapTest, TCPBlockingConnectFailsArpResolution) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor sender = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); - - sockaddr_in connect_addr = { - .sin_family = AF_INET, - .sin_addr = {.s_addr = kTapPeerIPAddr}, - }; - ASSERT_THAT(connect(sender.get(), - reinterpret_cast<const struct sockaddr*>(&connect_addr), - sizeof(connect_addr)), - SyscallFailsWithErrno(EHOSTUNREACH)); -} - -// TCPNonBlockingConnectFailsArpResolution tests for TCP non-blocking connect to -// to trigger an error event to be notified to poll on link address resolution -// failure to a routable, but non existent peer. -TEST_F(TuntapTest, TCPNonBlockingConnectFailsArpResolution) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor sender = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); - - sockaddr_in connect_addr = { - .sin_family = AF_INET, - .sin_addr = {.s_addr = kTapPeerIPAddr}, - }; - ASSERT_THAT(connect(sender.get(), - reinterpret_cast<const struct sockaddr*>(&connect_addr), - sizeof(connect_addr)), - SyscallFailsWithErrno(EINPROGRESS)); - - constexpr int kTimeout = 10000; - struct pollfd pfd = { - .fd = sender.get(), - .events = POLLIN | POLLOUT, - }; - ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - ASSERT_EQ(pfd.revents, POLLIN | POLLOUT | POLLHUP | POLLERR); - - ASSERT_THAT(connect(sender.get(), - reinterpret_cast<const struct sockaddr*>(&connect_addr), - sizeof(connect_addr)), - SyscallFailsWithErrno(EHOSTUNREACH)); -} - -// Write hang bug found by syskaller: b/155928773 -// https://syzkaller.appspot.com/bug?id=065b893bd8d1d04a4e0a1d53c578537cde1efe99 -TEST_F(TuntapTest, WriteHangBug155928773) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); - - int sock = socket(AF_INET, SOCK_DGRAM, 0); - ASSERT_THAT(sock, SyscallSucceeds()); - - struct sockaddr_in remote = { - .sin_family = AF_INET, - .sin_port = htons(42), - .sin_addr = {.s_addr = kTapIPAddr}, - }; - // Return values do not matter in this test. - connect(sock, reinterpret_cast<struct sockaddr*>(&remote), sizeof(remote)); - write(sock, "hello", 5); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/tuntap_hostinet.cc b/test/syscalls/linux/tuntap_hostinet.cc deleted file mode 100644 index 1513fb9d5..000000000 --- a/test/syscalls/linux/tuntap_hostinet.cc +++ /dev/null @@ -1,38 +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. - -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(TuntapHostInetTest, NoNetTun) { - SKIP_IF(!IsRunningOnGvisor()); - SKIP_IF(!IsRunningWithHostinet()); - - struct stat statbuf; - ASSERT_THAT(stat("/dev/net/tun", &statbuf), SyscallFailsWithErrno(ENOENT)); -} - -} // namespace -} // namespace testing - -} // namespace gvisor diff --git a/test/syscalls/linux/udp_bind.cc b/test/syscalls/linux/udp_bind.cc deleted file mode 100644 index 6d92bdbeb..000000000 --- a/test/syscalls/linux/udp_bind.cc +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include "gtest/gtest.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -struct sockaddr_in_common { - sa_family_t sin_family; - in_port_t sin_port; -}; - -struct SendtoTestParam { - // Human readable description of test parameter. - std::string description; - - // Test is broken in gVisor, skip. - bool skip_on_gvisor; - - // Domain for the socket that will do the sending. - int send_domain; - - // Address to bind for the socket that will do the sending. - struct sockaddr_storage send_addr; - socklen_t send_addr_len; // 0 for unbound. - - // Address to connect to for the socket that will do the sending. - struct sockaddr_storage connect_addr; - socklen_t connect_addr_len; // 0 for no connection. - - // Domain for the socket that will do the receiving. - int recv_domain; - - // Address to bind for the socket that will do the receiving. - struct sockaddr_storage recv_addr; - socklen_t recv_addr_len; - - // Address to send to. - struct sockaddr_storage sendto_addr; - socklen_t sendto_addr_len; - - // Expected errno for the sendto call. - std::vector<int> sendto_errnos; // empty on success. -}; - -class SendtoTest : public ::testing::TestWithParam<SendtoTestParam> { - protected: - SendtoTest() { - // gUnit uses printf, so so will we. - printf("Testing with %s\n", GetParam().description.c_str()); - } -}; - -TEST_P(SendtoTest, Sendto) { - auto param = GetParam(); - - SKIP_IF(param.skip_on_gvisor && IsRunningOnGvisor()); - - const FileDescriptor s1 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(param.send_domain, SOCK_DGRAM, 0)); - const FileDescriptor s2 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(param.recv_domain, SOCK_DGRAM, 0)); - - if (param.send_addr_len > 0) { - ASSERT_THAT(bind(s1.get(), reinterpret_cast<sockaddr*>(¶m.send_addr), - param.send_addr_len), - SyscallSucceeds()); - } - - if (param.connect_addr_len > 0) { - ASSERT_THAT( - connect(s1.get(), reinterpret_cast<sockaddr*>(¶m.connect_addr), - param.connect_addr_len), - SyscallSucceeds()); - } - - ASSERT_THAT(bind(s2.get(), reinterpret_cast<sockaddr*>(¶m.recv_addr), - param.recv_addr_len), - SyscallSucceeds()); - - struct sockaddr_storage real_recv_addr = {}; - socklen_t real_recv_addr_len = param.recv_addr_len; - ASSERT_THAT( - getsockname(s2.get(), reinterpret_cast<sockaddr*>(&real_recv_addr), - &real_recv_addr_len), - SyscallSucceeds()); - - ASSERT_EQ(real_recv_addr_len, param.recv_addr_len); - - int recv_port = - reinterpret_cast<sockaddr_in_common*>(&real_recv_addr)->sin_port; - - struct sockaddr_storage sendto_addr = param.sendto_addr; - reinterpret_cast<sockaddr_in_common*>(&sendto_addr)->sin_port = recv_port; - - char buf[20] = {}; - if (!param.sendto_errnos.empty()) { - ASSERT_THAT(RetryEINTR(sendto)(s1.get(), buf, sizeof(buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr), - param.sendto_addr_len), - SyscallFailsWithErrno(ElementOf(param.sendto_errnos))); - return; - } - - ASSERT_THAT(RetryEINTR(sendto)(s1.get(), buf, sizeof(buf), 0, - reinterpret_cast<sockaddr*>(&sendto_addr), - param.sendto_addr_len), - SyscallSucceedsWithValue(sizeof(buf))); - - struct sockaddr_storage got_addr = {}; - socklen_t got_addr_len = sizeof(sockaddr_storage); - ASSERT_THAT(RetryEINTR(recvfrom)(s2.get(), buf, sizeof(buf), 0, - reinterpret_cast<sockaddr*>(&got_addr), - &got_addr_len), - SyscallSucceedsWithValue(sizeof(buf))); - - ASSERT_GT(got_addr_len, sizeof(sockaddr_in_common)); - int got_port = reinterpret_cast<sockaddr_in_common*>(&got_addr)->sin_port; - - struct sockaddr_storage sender_addr = {}; - socklen_t sender_addr_len = sizeof(sockaddr_storage); - ASSERT_THAT(getsockname(s1.get(), reinterpret_cast<sockaddr*>(&sender_addr), - &sender_addr_len), - SyscallSucceeds()); - - ASSERT_GT(sender_addr_len, sizeof(sockaddr_in_common)); - int sender_port = - reinterpret_cast<sockaddr_in_common*>(&sender_addr)->sin_port; - - EXPECT_EQ(got_port, sender_port); -} - -socklen_t Ipv4Addr(sockaddr_storage* addr, int port = 0) { - auto addr4 = reinterpret_cast<sockaddr_in*>(addr); - addr4->sin_family = AF_INET; - addr4->sin_port = port; - inet_pton(AF_INET, "127.0.0.1", &addr4->sin_addr.s_addr); - return sizeof(struct sockaddr_in); -} - -socklen_t Ipv6Addr(sockaddr_storage* addr, int port = 0) { - auto addr6 = reinterpret_cast<sockaddr_in6*>(addr); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = port; - inet_pton(AF_INET6, "::1", &addr6->sin6_addr.s6_addr); - return sizeof(struct sockaddr_in6); -} - -socklen_t Ipv4MappedIpv6Addr(sockaddr_storage* addr, int port = 0) { - auto addr6 = reinterpret_cast<sockaddr_in6*>(addr); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = port; - inet_pton(AF_INET6, "::ffff:127.0.0.1", &addr6->sin6_addr.s6_addr); - return sizeof(struct sockaddr_in6); -} - -INSTANTIATE_TEST_SUITE_P( - UdpBindTest, SendtoTest, - ::testing::Values( - []() { - SendtoTestParam param = {}; - param.description = "IPv4 mapped IPv6 sendto IPv4 mapped IPv6"; - param.send_domain = AF_INET6; - param.send_addr_len = Ipv4MappedIpv6Addr(¶m.send_addr); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv4MappedIpv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv6 sendto IPv6"; - param.send_domain = AF_INET6; - param.send_addr_len = Ipv6Addr(¶m.send_addr); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv4 sendto IPv4"; - param.send_domain = AF_INET; - param.send_addr_len = Ipv4Addr(¶m.send_addr); - param.recv_domain = AF_INET; - param.recv_addr_len = Ipv4Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv4 mapped IPv6 sendto IPv4"; - param.send_domain = AF_INET6; - param.send_addr_len = Ipv4MappedIpv6Addr(¶m.send_addr); - param.recv_domain = AF_INET; - param.recv_addr_len = Ipv4Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv4 sendto IPv4 mapped IPv6"; - param.send_domain = AF_INET; - param.send_addr_len = Ipv4Addr(¶m.send_addr); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv4MappedIpv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "unbound IPv6 sendto IPv4 mapped IPv6"; - param.send_domain = AF_INET6; - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv4MappedIpv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "unbound IPv6 sendto IPv4"; - param.send_domain = AF_INET6; - param.recv_domain = AF_INET; - param.recv_addr_len = Ipv4Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv6 sendto IPv4"; - param.send_domain = AF_INET6; - param.send_addr_len = Ipv6Addr(¶m.send_addr); - param.recv_domain = AF_INET; - param.recv_addr_len = Ipv4Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - param.sendto_errnos = {ENETUNREACH}; - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "IPv4 mapped IPv6 sendto IPv6"; - param.send_domain = AF_INET6; - param.send_addr_len = Ipv4MappedIpv6Addr(¶m.send_addr); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv6Addr(¶m.sendto_addr); - param.sendto_errnos = {EAFNOSUPPORT}; - // The errno returned changed in Linux commit c8e6ad0829a723. - param.sendto_errnos = {EINVAL, EAFNOSUPPORT}; - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "connected IPv4 mapped IPv6 sendto IPv6"; - param.send_domain = AF_INET6; - param.connect_addr_len = - Ipv4MappedIpv6Addr(¶m.connect_addr, 5000); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv6Addr(¶m.sendto_addr); - // The errno returned changed in Linux commit c8e6ad0829a723. - param.sendto_errnos = {EINVAL, EAFNOSUPPORT}; - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "connected IPv6 sendto IPv4 mapped IPv6"; - // TODO(igudger): Determine if this inconsistent behavior is worth - // implementing. - param.skip_on_gvisor = true; - param.send_domain = AF_INET6; - param.connect_addr_len = Ipv6Addr(¶m.connect_addr, 5000); - param.recv_domain = AF_INET6; - param.recv_addr_len = Ipv4MappedIpv6Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }(), - []() { - SendtoTestParam param = {}; - param.description = "connected IPv6 sendto IPv4"; - // TODO(igudger): Determine if this inconsistent behavior is worth - // implementing. - param.skip_on_gvisor = true; - param.send_domain = AF_INET6; - param.connect_addr_len = Ipv6Addr(¶m.connect_addr, 5000); - param.recv_domain = AF_INET; - param.recv_addr_len = Ipv4Addr(¶m.recv_addr); - param.sendto_addr_len = Ipv4MappedIpv6Addr(¶m.sendto_addr); - return param; - }())); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc deleted file mode 100644 index 16eeeb5c6..000000000 --- a/test/syscalls/linux/udp_socket.cc +++ /dev/null @@ -1,2153 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <fcntl.h> -#include <netinet/icmp6.h> -#include <netinet/ip_icmp.h> - -#include <ctime> - -#ifdef __linux__ -#include <linux/errqueue.h> -#include <linux/filter.h> -#endif // __linux__ -#include <netinet/in.h> -#include <poll.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include "absl/strings/str_format.h" -#ifndef SIOCGSTAMP -#include <linux/sockios.h> -#endif - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Fixture for tests parameterized by the address family to use (AF_INET and -// AF_INET6) when creating sockets. -class UdpSocketTest - : public ::testing::TestWithParam<gvisor::testing::AddressFamily> { - protected: - // Creates two sockets that will be used by test cases. - void SetUp() override; - - // Binds the socket bind_ to the loopback and updates bind_addr_. - PosixError BindLoopback(); - - // Binds the socket bind_ to Any and updates bind_addr_. - PosixError BindAny(); - - // Binds given socket to address addr and updates. - PosixError BindSocket(int socket, struct sockaddr* addr); - - // Return initialized Any address to port 0. - struct sockaddr_storage InetAnyAddr(); - - // Return initialized Loopback address to port 0. - struct sockaddr_storage InetLoopbackAddr(); - - // Disconnects socket sockfd. - void Disconnect(int sockfd); - - // Get family for the test. - int GetFamily(); - - // Socket used by Bind methods - FileDescriptor bind_; - - // Second socket used for tests. - FileDescriptor sock_; - - // Address for bind_ socket. - struct sockaddr* bind_addr_; - - // Initialized to the length based on GetFamily(). - socklen_t addrlen_; - - // Storage for bind_addr_. - struct sockaddr_storage bind_addr_storage_; - - private: - // Helper to initialize addrlen_ for the test case. - socklen_t GetAddrLength(); -}; - -// Gets a pointer to the port component of the given address. -uint16_t* Port(struct sockaddr_storage* addr) { - switch (addr->ss_family) { - case AF_INET: { - auto sin = reinterpret_cast<struct sockaddr_in*>(addr); - return &sin->sin_port; - } - case AF_INET6: { - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr); - return &sin6->sin6_port; - } - } - - return nullptr; -} - -// Sets addr port to "port". -void SetPort(struct sockaddr_storage* addr, uint16_t port) { - switch (addr->ss_family) { - case AF_INET: { - auto sin = reinterpret_cast<struct sockaddr_in*>(addr); - sin->sin_port = port; - break; - } - case AF_INET6: { - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr); - sin6->sin6_port = port; - break; - } - } -} - -void UdpSocketTest::SetUp() { - addrlen_ = GetAddrLength(); - - bind_ = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); - memset(&bind_addr_storage_, 0, sizeof(bind_addr_storage_)); - bind_addr_ = reinterpret_cast<struct sockaddr*>(&bind_addr_storage_); - - sock_ = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); -} - -int UdpSocketTest::GetFamily() { - if (GetParam() == AddressFamily::kIpv4) { - return AF_INET; - } - return AF_INET6; -} - -PosixError UdpSocketTest::BindLoopback() { - bind_addr_storage_ = InetLoopbackAddr(); - struct sockaddr* bind_addr_ = - reinterpret_cast<struct sockaddr*>(&bind_addr_storage_); - return BindSocket(bind_.get(), bind_addr_); -} - -PosixError UdpSocketTest::BindAny() { - bind_addr_storage_ = InetAnyAddr(); - struct sockaddr* bind_addr_ = - reinterpret_cast<struct sockaddr*>(&bind_addr_storage_); - return BindSocket(bind_.get(), bind_addr_); -} - -PosixError UdpSocketTest::BindSocket(int socket, struct sockaddr* addr) { - socklen_t len = sizeof(bind_addr_storage_); - - // Bind, then check that we get the right address. - RETURN_ERROR_IF_SYSCALL_FAIL(bind(socket, addr, addrlen_)); - - RETURN_ERROR_IF_SYSCALL_FAIL(getsockname(socket, addr, &len)); - - if (addrlen_ != len) { - return PosixError( - EINVAL, - absl::StrFormat("getsockname len: %u expected: %u", len, addrlen_)); - } - return PosixError(0); -} - -socklen_t UdpSocketTest::GetAddrLength() { - struct sockaddr_storage addr; - if (GetFamily() == AF_INET) { - auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); - return sizeof(*sin); - } - - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); - return sizeof(*sin6); -} - -sockaddr_storage UdpSocketTest::InetAnyAddr() { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - reinterpret_cast<struct sockaddr*>(&addr)->sa_family = GetFamily(); - - if (GetFamily() == AF_INET) { - auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); - sin->sin_addr.s_addr = htonl(INADDR_ANY); - sin->sin_port = htons(0); - return addr; - } - - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); - sin6->sin6_addr = IN6ADDR_ANY_INIT; - sin6->sin6_port = htons(0); - return addr; -} - -sockaddr_storage UdpSocketTest::InetLoopbackAddr() { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - reinterpret_cast<struct sockaddr*>(&addr)->sa_family = GetFamily(); - - if (GetFamily() == AF_INET) { - auto sin = reinterpret_cast<struct sockaddr_in*>(&addr); - sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sin->sin_port = htons(0); - return addr; - } - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr); - sin6->sin6_addr = in6addr_loopback; - sin6->sin6_port = htons(0); - return addr; -} - -void UdpSocketTest::Disconnect(int sockfd) { - sockaddr_storage addr_storage = InetAnyAddr(); - sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - socklen_t addrlen = sizeof(addr_storage); - - addr->sa_family = AF_UNSPEC; - ASSERT_THAT(connect(sockfd, addr, addrlen), SyscallSucceeds()); - - // Check that after disconnect the socket is bound to the ANY address. - EXPECT_THAT(getsockname(sockfd, addr, &addrlen), SyscallSucceeds()); - if (GetParam() == AddressFamily::kIpv4) { - auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr); - EXPECT_EQ(addrlen, sizeof(*addr_out)); - EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY)); - } else { - auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr); - EXPECT_EQ(addrlen, sizeof(*addr_out)); - struct in6_addr loopback = IN6ADDR_ANY_INIT; - - EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0); - } -} - -TEST_P(UdpSocketTest, Creation) { - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); - EXPECT_THAT(close(sock.release()), SyscallSucceeds()); - - sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, 0)); - EXPECT_THAT(close(sock.release()), SyscallSucceeds()); - - ASSERT_THAT(socket(GetFamily(), SOCK_STREAM, IPPROTO_UDP), SyscallFails()); -} - -TEST_P(UdpSocketTest, Getsockname) { - // Check that we're not bound. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - struct sockaddr_storage any = InetAnyAddr(); - EXPECT_EQ(memcmp(&addr, reinterpret_cast<struct sockaddr*>(&any), addrlen_), - 0); - - ASSERT_NO_ERRNO(BindLoopback()); - - EXPECT_THAT( - getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0); -} - -TEST_P(UdpSocketTest, Getpeername) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Check that we're not connected. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); - - // Connect, then check that we get the right address. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0); -} - -TEST_P(UdpSocketTest, SendNotConnected) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Do send & write, they must fail. - char buf[512]; - EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(EDESTADDRREQ)); - - EXPECT_THAT(write(sock_.get(), buf, sizeof(buf)), - SyscallFailsWithErrno(EDESTADDRREQ)); - - // Use sendto. - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Check that we're bound now. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_NE(*Port(&addr), 0); -} - -TEST_P(UdpSocketTest, ConnectBinds) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect the socket. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Check that we're bound now. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_NE(*Port(&addr), 0); -} - -TEST_P(UdpSocketTest, ReceiveNotBound) { - char buf[512]; - EXPECT_THAT(recv(sock_.get(), buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST_P(UdpSocketTest, Bind) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Try to bind again. - EXPECT_THAT(bind(bind_.get(), bind_addr_, addrlen_), - SyscallFailsWithErrno(EINVAL)); - - // Check that we're still bound to the original address. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0); -} - -TEST_P(UdpSocketTest, BindInUse) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Try to bind again. - EXPECT_THAT(bind(sock_.get(), bind_addr_, addrlen_), - SyscallFailsWithErrno(EADDRINUSE)); -} - -TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) { - // Discover a free unused port by creating a new UDP socket, binding it - // recording the just bound port and closing it. This is not guaranteed as it - // can still race with other port UDP sockets trying to bind a port at the - // same time. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - socklen_t addrlen = sizeof(addr_storage); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); - ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); - ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_NE(*Port(&addr_storage), 0); - ASSERT_THAT(close(s.release()), SyscallSucceeds()); - - // Now connect to the port that we just released. This should generate an - // ECONNREFUSED error. - ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - // Send from sock_ to an unbound port. - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Now verify that we got an ICMP error back of ECONNREFUSED. - int err; - socklen_t optlen = sizeof(err); - ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), - SyscallSucceeds()); - ASSERT_EQ(err, ECONNREFUSED); - ASSERT_EQ(optlen, sizeof(err)); -} - -TEST_P(UdpSocketTest, ConnectSimultaneousWriteToInvalidPort) { - // Discover a free unused port by creating a new UDP socket, binding it - // recording the just bound port and closing it. This is not guaranteed as it - // can still race with other port UDP sockets trying to bind a port at the - // same time. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - socklen_t addrlen = sizeof(addr_storage); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - FileDescriptor s = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); - ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); - ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_NE(*Port(&addr_storage), 0); - ASSERT_THAT(close(s.release()), SyscallSucceeds()); - - // Now connect to the port that we just released. - ScopedThread t([&] { - ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); - }); - - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - // Send from sock_ to an unbound port. - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - t.Join(); -} - -TEST_P(UdpSocketTest, ReceiveAfterConnect) { - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Send from sock_ to bind_ - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Receive the data. - char received[sizeof(buf)]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); -} - -TEST_P(UdpSocketTest, ReceiveAfterDisconnect) { - ASSERT_NO_ERRNO(BindLoopback()); - - for (int i = 0; i < 2; i++) { - // Connet sock_ to bound address. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - - // Send from sock to bind_. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - ASSERT_THAT(sendto(bind_.get(), buf, sizeof(buf), 0, - reinterpret_cast<sockaddr*>(&addr), addrlen), - SyscallSucceedsWithValue(sizeof(buf))); - - // Receive the data. - char received[sizeof(buf)]; - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); - - // Disconnect sock_. - struct sockaddr unspec = {}; - unspec.sa_family = AF_UNSPEC; - ASSERT_THAT(connect(sock_.get(), &unspec, sizeof(unspec.sa_family)), - SyscallSucceeds()); - } -} - -TEST_P(UdpSocketTest, Connect) { - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Check that we're connected to the right peer. - struct sockaddr_storage peer; - socklen_t peerlen = sizeof(peer); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen), - SyscallSucceeds()); - EXPECT_EQ(peerlen, addrlen_); - EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0); - - // Try to bind after connect. - struct sockaddr_storage any = InetAnyAddr(); - EXPECT_THAT( - bind(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_), - SyscallFailsWithErrno(EINVAL)); - - struct sockaddr_storage bind2_storage = InetLoopbackAddr(); - struct sockaddr* bind2_addr = - reinterpret_cast<struct sockaddr*>(&bind2_storage); - FileDescriptor bind2 = - ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); - ASSERT_NO_ERRNO(BindSocket(bind2.get(), bind2_addr)); - - // Try to connect again. - EXPECT_THAT(connect(sock_.get(), bind2_addr, addrlen_), SyscallSucceeds()); - - // Check that peer name changed. - peerlen = sizeof(peer); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen), - SyscallSucceeds()); - EXPECT_EQ(peerlen, addrlen_); - EXPECT_EQ(memcmp(&peer, bind2_addr, addrlen_), 0); -} - -TEST_P(UdpSocketTest, ConnectAnyZero) { - // TODO(138658473): Enable when we can connect to port 0 with gVisor. - SKIP_IF(IsRunningOnGvisor()); - - struct sockaddr_storage any = InetAnyAddr(); - EXPECT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_), - SyscallSucceeds()); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(UdpSocketTest, ConnectAnyWithPort) { - ASSERT_NO_ERRNO(BindAny()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); -} - -TEST_P(UdpSocketTest, DisconnectAfterConnectAny) { - // TODO(138658473): Enable when we can connect to port 0 with gVisor. - SKIP_IF(IsRunningOnGvisor()); - struct sockaddr_storage any = InetAnyAddr(); - EXPECT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_), - SyscallSucceeds()); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); - - Disconnect(sock_.get()); -} - -TEST_P(UdpSocketTest, DisconnectAfterConnectAnyWithPort) { - ASSERT_NO_ERRNO(BindAny()); - EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(*Port(&bind_addr_storage_), *Port(&addr)); - - Disconnect(sock_.get()); -} - -TEST_P(UdpSocketTest, DisconnectAfterBind) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Bind to the next port above bind_. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr)); - - // Connect the socket. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - struct sockaddr_storage unspec = {}; - unspec.ss_family = AF_UNSPEC; - EXPECT_THAT(connect(sock_.get(), reinterpret_cast<sockaddr*>(&unspec), - sizeof(unspec.ss_family)), - SyscallSucceeds()); - - // Check that we're still bound. - socklen_t addrlen = sizeof(unspec); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&unspec), &addrlen), - SyscallSucceeds()); - - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(memcmp(addr, &unspec, addrlen_), 0); - - addrlen = sizeof(addr); - EXPECT_THAT(getpeername(sock_.get(), addr, &addrlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(UdpSocketTest, BindToAnyConnnectToLocalhost) { - ASSERT_NO_ERRNO(BindAny()); - - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - socklen_t addrlen = sizeof(addr); - - // Connect the socket. - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - EXPECT_THAT(getsockname(bind_.get(), addr, &addrlen), SyscallSucceeds()); - - // If the socket is bound to ANY and connected to a loopback address, - // getsockname() has to return the loopback address. - if (GetParam() == AddressFamily::kIpv4) { - auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr); - EXPECT_EQ(addrlen, sizeof(*addr_out)); - EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_LOOPBACK)); - } else { - auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr); - struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT; - EXPECT_EQ(addrlen, sizeof(*addr_out)); - EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0); - } -} - -TEST_P(UdpSocketTest, DisconnectAfterBindToAny) { - ASSERT_NO_ERRNO(BindLoopback()); - - struct sockaddr_storage any_storage = InetAnyAddr(); - struct sockaddr* any = reinterpret_cast<struct sockaddr*>(&any_storage); - SetPort(&any_storage, *Port(&bind_addr_storage_) + 1); - - ASSERT_NO_ERRNO(BindSocket(sock_.get(), any)); - - // Connect the socket. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - Disconnect(sock_.get()); - - // Check that we're still bound. - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(memcmp(&addr, any, addrlen), 0); - - addrlen = sizeof(addr); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(UdpSocketTest, Disconnect) { - ASSERT_NO_ERRNO(BindLoopback()); - - struct sockaddr_storage any_storage = InetAnyAddr(); - struct sockaddr* any = reinterpret_cast<struct sockaddr*>(&any_storage); - SetPort(&any_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_NO_ERRNO(BindSocket(sock_.get(), any)); - - for (int i = 0; i < 2; i++) { - // Try to connect again. - EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Check that we're connected to the right peer. - struct sockaddr_storage peer; - socklen_t peerlen = sizeof(peer); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen), - SyscallSucceeds()); - EXPECT_EQ(peerlen, addrlen_); - EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0); - - // Try to disconnect. - struct sockaddr_storage addr = {}; - addr.ss_family = AF_UNSPEC; - EXPECT_THAT(connect(sock_.get(), reinterpret_cast<sockaddr*>(&addr), - sizeof(addr.ss_family)), - SyscallSucceeds()); - - peerlen = sizeof(peer); - EXPECT_THAT( - getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen), - SyscallFailsWithErrno(ENOTCONN)); - - // Check that we're still bound. - socklen_t addrlen = sizeof(addr); - EXPECT_THAT( - getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen), - SyscallSucceeds()); - EXPECT_EQ(addrlen, addrlen_); - EXPECT_EQ(*Port(&addr), *Port(&any_storage)); - } -} - -TEST_P(UdpSocketTest, ConnectBadAddress) { - struct sockaddr addr = {}; - addr.sa_family = GetFamily(); - ASSERT_THAT(connect(sock_.get(), &addr, sizeof(addr.sa_family)), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) { - ASSERT_NO_ERRNO(BindLoopback()); - - struct sockaddr_storage addr_storage = InetAnyAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Send to a different destination than we're connected to. - char buf[512]; - EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) { - ASSERT_NO_ERRNO(BindLoopback()); - // Close the socket to release the port so that we get an ICMP error. - ASSERT_THAT(close(bind_.release()), SyscallSucceeds()); - - // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an - // UDP socket. There is no easy way to ensure that the UDP port is not bound - // by another conncurrently running test. *This is potentially flaky*. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - char buf[512]; - EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0), - SyscallSucceedsWithValue(sizeof(buf))); - - constexpr int kTimeout = 1000; - // Poll to make sure we get the ICMP error back before issuing more writes. - struct pollfd pfd = {sock_.get(), POLLERR, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - - // Next write should fail with ECONNREFUSED due to the ICMP error generated in - // response to the previous write. - ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(ECONNREFUSED)); - - // The next write should succeed again since the last write call would have - // retrieved and cleared the socket error. - ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds()); - - // Poll to make sure we get the ICMP error back before issuing more writes. - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); - - // Next write should fail with ECONNREFUSED due to the ICMP error generated in - // response to the previous write. - ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), - SyscallFailsWithErrno(ECONNREFUSED)); -} - -#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 - // TODO(b/176251997): The next check fails on the gvisor platform due to the - // kernel bug. - if (!IsRunningWithHostinet() || GvisorPlatform() == Platform::kPtrace || - GvisorPlatform() == Platform::kKVM || - GvisorPlatform() == Platform::kNative) - 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()); - - ASSERT_NO_ERRNO(BindLoopback()); - // Connect to loopback:bind_addr_+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind sock to loopback:bind_addr_+1. - ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds()); - - char buf[3]; - // Send zero length packet from bind_ to sock_. - ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0)); - - struct pollfd pfd = {sock_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout*/ 1000), - SyscallSucceedsWithValue(1)); - - // Receive the packet. - char received[3]; - EXPECT_THAT(read(sock_.get(), received, sizeof(received)), - SyscallSucceedsWithValue(0)); -} - -TEST_P(UdpSocketTest, ZerolengthWriteAllowedNonBlockRead) { - // TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind sock to loopback:bind_addr_port+1. - ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds()); - - // Set sock to non-blocking. - int opts = 0; - ASSERT_THAT(opts = fcntl(sock_.get(), F_GETFL), SyscallSucceeds()); - ASSERT_THAT(fcntl(sock_.get(), F_SETFL, opts | O_NONBLOCK), - SyscallSucceeds()); - - char buf[3]; - // Send zero length packet from bind_ to sock_. - ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0)); - - struct pollfd pfd = {sock_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // Receive the packet. - char received[3]; - EXPECT_THAT(read(sock_.get(), received, sizeof(received)), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(read(sock_.get(), received, sizeof(received)), - SyscallFailsWithErrno(EAGAIN)); -} - -TEST_P(UdpSocketTest, SendAndReceiveNotConnected) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Send some data to bind_. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Receive the data. - char received[sizeof(buf)]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); -} - -TEST_P(UdpSocketTest, SendAndReceiveConnected) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind sock to loopback:bind_addr_port+1. - ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds()); - - // Send some data from sock to bind_. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Receive the data. - char received[sizeof(buf)]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); -} - -TEST_P(UdpSocketTest, ReceiveFromNotConnected) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind sock to loopback:bind_addr_port+2. - struct sockaddr_storage addr2_storage = InetLoopbackAddr(); - struct sockaddr* addr2 = reinterpret_cast<struct sockaddr*>(&addr2_storage); - SetPort(&addr2_storage, *Port(&bind_addr_storage_) + 2); - ASSERT_THAT(bind(sock_.get(), addr2, addrlen_), SyscallSucceeds()); - - // Send some data from sock to bind_. - char buf[512]; - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Check that the data isn't received because it was sent from a different - // address than we're connected. - EXPECT_THAT(recv(sock_.get(), buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST_P(UdpSocketTest, ReceiveBeforeConnect) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Bind sock to loopback:bind_addr_port+2. - struct sockaddr_storage addr2_storage = InetLoopbackAddr(); - struct sockaddr* addr2 = reinterpret_cast<struct sockaddr*>(&addr2_storage); - SetPort(&addr2_storage, *Port(&bind_addr_storage_) + 2); - ASSERT_THAT(bind(sock_.get(), addr2, addrlen_), SyscallSucceeds()); - - // Send some data from sock to bind_. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Receive the data. It works because it was sent before the connect. - char received[sizeof(buf)]; - EXPECT_THAT( - RecvTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/), - IsPosixErrorOkAndHolds(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); - - // Send again. This time it should not be received. - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - EXPECT_THAT(recv(bind_.get(), buf, sizeof(buf), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST_P(UdpSocketTest, ReceiveFrom) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind sock to loopback:bind_addr_port+1. - ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds()); - - // Send some data from sock to bind_. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - - // Receive the data and sender address. - char received[sizeof(buf)]; - struct sockaddr_storage addr2; - socklen_t addr2len = sizeof(addr2); - EXPECT_THAT(recvfrom(bind_.get(), received, sizeof(received), 0, - reinterpret_cast<sockaddr*>(&addr2), &addr2len), - SyscallSucceedsWithValue(sizeof(received))); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); - EXPECT_EQ(addr2len, addrlen_); - EXPECT_EQ(memcmp(addr, &addr2, addrlen_), 0); -} - -TEST_P(UdpSocketTest, Listen) { - ASSERT_THAT(listen(sock_.get(), SOMAXCONN), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -TEST_P(UdpSocketTest, Accept) { - ASSERT_THAT(accept(sock_.get(), nullptr, nullptr), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -// This test validates that a read shutdown with pending data allows the read -// to proceed with the data before returning EAGAIN. -TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - // Bind to loopback:bind_addr_port+1 and connect to bind_addr_. - ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Verify that we get EWOULDBLOCK when there is nothing to read. - char received[512]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - const char* buf = "abc"; - EXPECT_THAT(write(sock_.get(), buf, 3), SyscallSucceedsWithValue(3)); - - int opts = 0; - ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds()); - ASSERT_THAT(fcntl(bind_.get(), F_SETFL, opts | O_NONBLOCK), - SyscallSucceeds()); - ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds()); - ASSERT_NE(opts & O_NONBLOCK, 0); - - EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds()); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // We should get the data even though read has been shutdown. - EXPECT_THAT(RecvTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/), - IsPosixErrorOkAndHolds(2)); - - // Because we read less than the entire packet length, since it's a packet - // based socket any subsequent reads should return EWOULDBLOCK. - EXPECT_THAT(recv(bind_.get(), received, 1, 0), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -// This test is validating that even after a socket is shutdown if it's -// reconnected it will reset the shutdown state. -TEST_P(UdpSocketTest, ReadShutdownSameSocketResetsShutdownState) { - char received[512]; - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); - - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Connect the socket, then try to shutdown again. - ASSERT_NO_ERRNO(BindLoopback()); - - // Connect to loopback:bind_addr_port+1. - struct sockaddr_storage addr_storage = InetLoopbackAddr(); - struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); - SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1); - ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds()); - - EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); -} - -TEST_P(UdpSocketTest, ReadShutdown) { - // TODO(gvisor.dev/issue/1202): Calling recv() after shutdown without - // MSG_DONTWAIT blocks indefinitely. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - - char received[512]; - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); - - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Connect the socket, then try to shutdown again. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds()); - - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(0)); -} - -TEST_P(UdpSocketTest, ReadShutdownDifferentThread) { - // TODO(gvisor.dev/issue/1202): Calling recv() after shutdown without - // MSG_DONTWAIT blocks indefinitely. - SKIP_IF(IsRunningWithHostinet()); - ASSERT_NO_ERRNO(BindLoopback()); - - char received[512]; - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Connect the socket, then shutdown from another thread. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - ScopedThread t([&] { - absl::SleepFor(absl::Milliseconds(200)); - EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds()); - }); - EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(0)); - t.Join(); - - EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(0)); -} - -TEST_P(UdpSocketTest, WriteShutdown) { - ASSERT_NO_ERRNO(BindLoopback()); - EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallSucceeds()); -} - -TEST_P(UdpSocketTest, SynchronousReceive) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Send some data to bind_ from another thread. - char buf[512]; - RandomizeBuffer(buf, sizeof(buf)); - - // Receive the data prior to actually starting the other thread. - char received[512]; - EXPECT_THAT( - RetryEINTR(recv)(bind_.get(), received, sizeof(received), MSG_DONTWAIT), - SyscallFailsWithErrno(EWOULDBLOCK)); - - // Start the thread. - ScopedThread t([&] { - absl::SleepFor(absl::Milliseconds(200)); - ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, this->bind_addr_, - this->addrlen_), - SyscallSucceedsWithValue(sizeof(buf))); - }); - - EXPECT_THAT(RetryEINTR(recv)(bind_.get(), received, sizeof(received), 0), - SyscallSucceedsWithValue(512)); - EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0); -} - -TEST_P(UdpSocketTest, BoundaryPreserved_SendRecv) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Send 3 packets from sock to bind_. - constexpr int psize = 100; - char buf[3 * psize]; - RandomizeBuffer(buf, sizeof(buf)); - - for (int i = 0; i < 3; ++i) { - ASSERT_THAT( - sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(psize)); - } - - // Receive the data as 3 separate packets. - char received[6 * psize]; - for (int i = 0; i < 3; ++i) { - EXPECT_THAT(recv(bind_.get(), received + i * psize, 3 * psize, 0), - SyscallSucceedsWithValue(psize)); - } - EXPECT_EQ(memcmp(buf, received, 3 * psize), 0); -} - -TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Direct writes from sock to bind_. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Send 2 packets from sock to bind_, where each packet's data consists of - // 2 discontiguous iovecs. - constexpr size_t kPieceSize = 100; - char buf[4 * kPieceSize]; - RandomizeBuffer(buf, sizeof(buf)); - - for (int i = 0; i < 2; i++) { - struct iovec iov[2]; - for (int j = 0; j < 2; j++) { - iov[j].iov_base = reinterpret_cast<void*>( - reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize); - iov[j].iov_len = kPieceSize; - } - ASSERT_THAT(writev(sock_.get(), iov, 2), - SyscallSucceedsWithValue(2 * kPieceSize)); - } - - // Receive the data as 2 separate packets. - char received[6 * kPieceSize]; - for (int i = 0; i < 2; i++) { - struct iovec iov[3]; - for (int j = 0; j < 3; j++) { - iov[j].iov_base = reinterpret_cast<void*>( - reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize); - iov[j].iov_len = kPieceSize; - } - ASSERT_THAT(readv(bind_.get(), iov, 3), - SyscallSucceedsWithValue(2 * kPieceSize)); - } - EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0); -} - -TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Send 2 packets from sock to bind_, where each packet's data consists of - // 2 discontiguous iovecs. - constexpr size_t kPieceSize = 100; - char buf[4 * kPieceSize]; - RandomizeBuffer(buf, sizeof(buf)); - - for (int i = 0; i < 2; i++) { - struct iovec iov[2]; - for (int j = 0; j < 2; j++) { - iov[j].iov_base = reinterpret_cast<void*>( - reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize); - iov[j].iov_len = kPieceSize; - } - struct msghdr msg = {}; - msg.msg_name = bind_addr_; - msg.msg_namelen = addrlen_; - msg.msg_iov = iov; - msg.msg_iovlen = 2; - ASSERT_THAT(sendmsg(sock_.get(), &msg, 0), - SyscallSucceedsWithValue(2 * kPieceSize)); - } - - // Receive the data as 2 separate packets. - char received[6 * kPieceSize]; - for (int i = 0; i < 2; i++) { - struct iovec iov[3]; - for (int j = 0; j < 3; j++) { - iov[j].iov_base = reinterpret_cast<void*>( - reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize); - iov[j].iov_len = kPieceSize; - } - struct msghdr msg = {}; - msg.msg_iov = iov; - msg.msg_iovlen = 3; - ASSERT_THAT(recvmsg(bind_.get(), &msg, 0), - SyscallSucceedsWithValue(2 * kPieceSize)); - } - EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0); -} - -TEST_P(UdpSocketTest, FIONREADShutdown) { - ASSERT_NO_ERRNO(BindLoopback()); - - int n = -1; - EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - // A UDP socket must be connected before it can be shutdown. - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); -} - -TEST_P(UdpSocketTest, FIONREADWriteShutdown) { - int n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - ASSERT_NO_ERRNO(BindLoopback()); - - // A UDP socket must be connected before it can be shutdown. - ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - const char str[] = "abc"; - ASSERT_THAT(send(bind_.get(), str, sizeof(str), 0), - SyscallSucceedsWithValue(sizeof(str))); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, sizeof(str)); - - EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, sizeof(str)); -} - -// NOTE: Do not use `FIONREAD` as test name because it will be replaced by the -// corresponding macro and become `0x541B`. -TEST_P(UdpSocketTest, Fionread) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Check that the bound socket with an empty buffer reports an empty first - // packet. - int n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - // Send 3 packets from sock to bind_. - constexpr int psize = 100; - char buf[3 * psize]; - RandomizeBuffer(buf, sizeof(buf)); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - for (int i = 0; i < 3; ++i) { - ASSERT_THAT( - sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(psize)); - - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // Check that regardless of how many packets are in the queue, the size - // reported is that of a single packet. - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, psize); - } -} - -TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) { - ASSERT_NO_ERRNO(BindLoopback()); - - // Check that the bound socket with an empty buffer reports an empty first - // packet. - int n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - // Send 3 packets from sock to bind_. - constexpr int psize = 100; - char buf[3 * psize]; - RandomizeBuffer(buf, sizeof(buf)); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - for (int i = 0; i < 3; ++i) { - ASSERT_THAT( - sendto(sock_.get(), buf + i * psize, 0, 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(0)); - - // TODO(gvisor.dev/issue/2726): sending a zero-length message to a hostinet - // socket does not cause a poll event to be triggered. - if (!IsRunningWithHostinet()) { - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - } - - // Check that regardless of how many packets are in the queue, the size - // reported is that of a single packet. - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - } -} - -TEST_P(UdpSocketTest, FIONREADZeroLengthWriteShutdown) { - int n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - ASSERT_NO_ERRNO(BindLoopback()); - - // A UDP socket must be connected before it can be shutdown. - ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - const char str[] = "abc"; - ASSERT_THAT(send(bind_.get(), str, 0, 0), SyscallSucceedsWithValue(0)); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); - - EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds()); - - n = -1; - EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0)); - EXPECT_EQ(n, 0); -} - -TEST_P(UdpSocketTest, SoNoCheckOffByDefault) { - // TODO(gvisor.dev/issue/1202): SO_NO_CHECK socket option not supported by - // hostinet. - SKIP_IF(IsRunningWithHostinet()); - - int v = -1; - socklen_t optlen = sizeof(v); - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen), - SyscallSucceeds()); - ASSERT_EQ(v, kSockOptOff); - ASSERT_EQ(optlen, sizeof(v)); -} - -TEST_P(UdpSocketTest, SoNoCheck) { - // TODO(gvisor.dev/issue/1202): SO_NO_CHECK socket option not supported by - // hostinet. - SKIP_IF(IsRunningWithHostinet()); - - int v = kSockOptOn; - socklen_t optlen = sizeof(v); - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen), - SyscallSucceeds()); - v = -1; - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen), - SyscallSucceeds()); - ASSERT_EQ(v, kSockOptOn); - ASSERT_EQ(optlen, sizeof(v)); - - v = kSockOptOff; - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen), - SyscallSucceeds()); - v = -1; - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen), - SyscallSucceeds()); - ASSERT_EQ(v, kSockOptOff); - ASSERT_EQ(optlen, sizeof(v)); -} - -#ifdef __linux__ -TEST_P(UdpSocketTest, ErrorQueue) { - char cmsgbuf[CMSG_SPACE(sizeof(sock_extended_err))]; - msghdr msg; - memset(&msg, 0, sizeof(msg)); - iovec iov; - memset(&iov, 0, sizeof(iov)); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - - // recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT. - EXPECT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, MSG_ERRQUEUE), - SyscallFailsWithErrno(EAGAIN)); -} -#endif // __linux__ - -TEST_P(UdpSocketTest, SoTimestampOffByDefault) { - // TODO(gvisor.dev/issue/1202): SO_TIMESTAMP socket option not supported by - // hostinet. - SKIP_IF(IsRunningWithHostinet()); - - int v = -1; - socklen_t optlen = sizeof(v); - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, &optlen), - SyscallSucceeds()); - ASSERT_EQ(v, kSockOptOff); - ASSERT_EQ(optlen, sizeof(v)); -} - -TEST_P(UdpSocketTest, SoTimestamp) { - // TODO(gvisor.dev/issue/1202): ioctl() and SO_TIMESTAMP socket option are not - // supported by hostinet. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - int v = 1; - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)), - SyscallSucceeds()); - - char buf[3]; - // Send zero length packet from sock to bind_. - ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0), - SyscallSucceedsWithValue(0)); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))]; - msghdr msg; - memset(&msg, 0, sizeof(msg)); - iovec iov; - memset(&iov, 0, sizeof(iov)); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0), - SyscallSucceedsWithValue(0)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SO_TIMESTAMP); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct timeval))); - - struct timeval tv = {}; - memcpy(&tv, CMSG_DATA(cmsg), sizeof(struct timeval)); - - ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0); - - // There should be nothing to get via ioctl. - ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(UdpSocketTest, WriteShutdownNotConnected) { - EXPECT_THAT(shutdown(bind_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); -} - -TEST_P(UdpSocketTest, TimestampIoctl) { - // TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - char buf[3]; - // Send packet from sock to bind_. - ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // There should be no control messages. - char recv_buf[sizeof(buf)]; - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf))); - - // A nonzero timeval should be available via ioctl. - struct timeval tv = {}; - ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds()); - ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0); -} - -TEST_P(UdpSocketTest, TimestampIoctlNothingRead) { - // TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - struct timeval tv = {}; - ASSERT_THAT(ioctl(sock_.get(), SIOCGSTAMP, &tv), - SyscallFailsWithErrno(ENOENT)); -} - -// Test that the timestamp accessed via SIOCGSTAMP is still accessible after -// SO_TIMESTAMP is enabled and used to retrieve a timestamp. -TEST_P(UdpSocketTest, TimestampIoctlPersistence) { - // TODO(gvisor.dev/issue/1202): ioctl() and SO_TIMESTAMP socket option are not - // supported by hostinet. - SKIP_IF(IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - char buf[3]; - // Send packet from sock to bind_. - ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)), - SyscallSucceedsWithValue(sizeof(buf))); - ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0), - SyscallSucceedsWithValue(0)); - - struct pollfd pfd = {bind_.get(), POLLIN, 0}; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // There should be no control messages. - char recv_buf[sizeof(buf)]; - ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf))); - - // A nonzero timeval should be available via ioctl. - struct timeval tv = {}; - ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds()); - ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0); - - // Enable SO_TIMESTAMP and send a message. - int v = 1; - EXPECT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)), - SyscallSucceeds()); - ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0), - SyscallSucceedsWithValue(0)); - - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000), - SyscallSucceedsWithValue(1)); - - // There should be a message for SO_TIMESTAMP. - char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))]; - msghdr msg = {}; - iovec iov = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0), - SyscallSucceedsWithValue(0)); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - - // The ioctl should return the exact same values as before. - struct timeval tv2 = {}; - ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv2), SyscallSucceeds()); - ASSERT_EQ(tv.tv_sec, tv2.tv_sec); - ASSERT_EQ(tv.tv_usec, tv2.tv_usec); -} - -// Test that a socket with IP_TOS or IPV6_TCLASS set will set the TOS byte on -// outgoing packets, and that a receiving socket with IP_RECVTOS or -// IPV6_RECVTCLASS will create the corresponding control message. -TEST_P(UdpSocketTest, SetAndReceiveTOS) { - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Allow socket to receive control message. - int recv_level = SOL_IP; - int recv_type = IP_RECVTOS; - if (GetParam() != AddressFamily::kIpv4) { - recv_level = SOL_IPV6; - recv_type = IPV6_RECVTCLASS; - } - ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Set socket TOS. - int sent_level = recv_level; - int sent_type = IP_TOS; - if (sent_level == SOL_IPV6) { - sent_type = IPV6_TCLASS; - } - int sent_tos = IPTOS_LOWDELAY; // Choose some TOS value. - ASSERT_THAT(setsockopt(sock_.get(), sent_level, sent_type, &sent_tos, - sizeof(sent_tos)), - SyscallSucceeds()); - - // Prepare message to send. - constexpr size_t kDataLength = 1024; - struct msghdr sent_msg = {}; - struct iovec sent_iov = {}; - char sent_data[kDataLength]; - sent_iov.iov_base = &sent_data[0]; - sent_iov.iov_len = kDataLength; - sent_msg.msg_iov = &sent_iov; - sent_msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - // Receive message. - struct msghdr received_msg = {}; - struct iovec received_iov = {}; - char received_data[kDataLength]; - received_iov.iov_base = &received_data[0]; - received_iov.iov_len = kDataLength; - received_msg.msg_iov = &received_iov; - received_msg.msg_iovlen = 1; - size_t cmsg_data_len = sizeof(int8_t); - if (sent_type == IPV6_TCLASS) { - cmsg_data_len = sizeof(int); - } - std::vector<char> received_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - received_msg.msg_control = &received_cmsgbuf[0]; - received_msg.msg_controllen = received_cmsgbuf.size(); - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - struct 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, sent_level); - EXPECT_EQ(cmsg->cmsg_type, sent_type); - int8_t received_tos = 0; - memcpy(&received_tos, CMSG_DATA(cmsg), sizeof(received_tos)); - EXPECT_EQ(received_tos, sent_tos); -} - -// Test that sendmsg with IP_TOS and IPV6_TCLASS control messages will set the -// TOS byte on outgoing packets, and that a receiving socket with IP_RECVTOS or -// IPV6_RECVTCLASS will create the corresponding control message. -TEST_P(UdpSocketTest, SendAndReceiveTOS) { - // TODO(b/146661005): Setting TOS via cmsg not supported for netstack. - SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Allow socket to receive control message. - int recv_level = SOL_IP; - int recv_type = IP_RECVTOS; - if (GetParam() != AddressFamily::kIpv4) { - recv_level = SOL_IPV6; - recv_type = IPV6_RECVTCLASS; - } - int recv_opt = kSockOptOn; - ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &recv_opt, - sizeof(recv_opt)), - SyscallSucceeds()); - - // Prepare message to send. - constexpr size_t kDataLength = 1024; - int sent_level = recv_level; - int sent_type = IP_TOS; - int sent_tos = IPTOS_LOWDELAY; // Choose some TOS value. - - struct msghdr sent_msg = {}; - struct iovec sent_iov = {}; - char sent_data[kDataLength]; - sent_iov.iov_base = &sent_data[0]; - sent_iov.iov_len = kDataLength; - sent_msg.msg_iov = &sent_iov; - sent_msg.msg_iovlen = 1; - size_t cmsg_data_len = sizeof(int8_t); - if (sent_level == SOL_IPV6) { - sent_type = IPV6_TCLASS; - cmsg_data_len = sizeof(int); - } - std::vector<char> sent_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - sent_msg.msg_control = &sent_cmsgbuf[0]; - sent_msg.msg_controllen = CMSG_LEN(cmsg_data_len); - - // Manually add control message. - struct cmsghdr* sent_cmsg = CMSG_FIRSTHDR(&sent_msg); - sent_cmsg->cmsg_len = CMSG_LEN(cmsg_data_len); - sent_cmsg->cmsg_level = sent_level; - sent_cmsg->cmsg_type = sent_type; - *(int8_t*)CMSG_DATA(sent_cmsg) = sent_tos; - - ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - // Receive message. - struct msghdr received_msg = {}; - struct iovec received_iov = {}; - char received_data[kDataLength]; - received_iov.iov_base = &received_data[0]; - received_iov.iov_len = kDataLength; - received_msg.msg_iov = &received_iov; - received_msg.msg_iovlen = 1; - std::vector<char> received_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - received_msg.msg_control = &received_cmsgbuf[0]; - received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - struct 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, sent_level); - EXPECT_EQ(cmsg->cmsg_type, sent_type); - int8_t received_tos = 0; - memcpy(&received_tos, CMSG_DATA(cmsg), sizeof(received_tos)); - EXPECT_EQ(received_tos, sent_tos); -} - -TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) { - // Discover minimum buffer size by setting it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - int min = 0; - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - - // Bind bind_ to loopback. - ASSERT_NO_ERRNO(BindLoopback()); - - { - // Send data of size min and verify that it's received. - std::vector<char> buf(min); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - std::vector<char> received(buf.size()); - EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(received.size())); - } - - { - // Send data of size min + 1 and verify that its received. Both linux and - // Netstack accept a dgram that exceeds rcvBuf limits if the receive buffer - // is currently empty. - std::vector<char> buf(min + 1); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - - std::vector<char> received(buf.size()); - ASSERT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(received.size())); - } -} - -// Test that receive buffer limits are enforced. -TEST_P(UdpSocketTest, RecvBufLimits) { - // Bind s_ to loopback. - ASSERT_NO_ERRNO(BindLoopback()); - - int min = 0; - { - // Discover minimum buffer size by trying to set it to zero. - constexpr int kRcvBufSz = 0; - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, - sizeof(kRcvBufSz)), - SyscallSucceeds()); - - socklen_t min_len = sizeof(min); - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len), - SyscallSucceeds()); - } - - // Now set the limit to min * 4. - int new_rcv_buf_sz = min * 4; - if (!IsRunningOnGvisor() || IsRunningWithHostinet()) { - // Linux doubles the value specified so just set to min * 2. - new_rcv_buf_sz = min * 2; - } - - ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &new_rcv_buf_sz, - sizeof(new_rcv_buf_sz)), - SyscallSucceeds()); - int rcv_buf_sz = 0; - { - socklen_t rcv_buf_len = sizeof(rcv_buf_sz); - ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz, - &rcv_buf_len), - SyscallSucceeds()); - } - - { - std::vector<char> buf(min); - RandomizeBuffer(buf.data(), buf.size()); - - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - int sent = 4; - if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { - // Linux seems to drop the 4th packet even though technically it should - // fit in the receive buffer. - ASSERT_THAT( - sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_), - SyscallSucceedsWithValue(buf.size())); - sent++; - } - - for (int i = 0; i < sent - 1; i++) { - // Receive the data. - std::vector<char> received(buf.size()); - EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(), - 1 /*timeout*/), - IsPosixErrorOkAndHolds(received.size())); - EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0); - } - - // The last receive should fail with EAGAIN as the last packet should have - // been dropped due to lack of space in the receive buffer. - std::vector<char> received(buf.size()); - EXPECT_THAT( - recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT), - SyscallFailsWithErrno(EAGAIN)); - } -} - -#ifdef __linux__ - -// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. -// gVisor currently silently ignores attaching a filter. -TEST_P(UdpSocketTest, SetSocketDetachFilter) { - // Program generated using sudo tcpdump -i lo udp and port 1234 -dd - struct sock_filter code[] = { - {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd}, - {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000011}, - {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x000004d2}, - {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x000004d2}, - {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017}, - {0x15, 0, 8, 0x00000011}, {0x28, 0, 0, 0x00000014}, - {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, - {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x000004d2}, - {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000004d2}, - {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000}, - }; - struct sock_fprog bpf = { - .len = ABSL_ARRAYSIZE(code), - .filter = code, - }; - ASSERT_THAT( - setsockopt(sock_.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)), - SyscallSucceeds()); - - constexpr int val = 0; - ASSERT_THAT( - setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallSucceeds()); -} - -#endif // __linux__ - -TEST_P(UdpSocketTest, SetSocketDetachFilterNoInstalledFilter) { - // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER. - SKIP_IF(IsRunningOnGvisor()); - constexpr int val = 0; - ASSERT_THAT( - setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)), - SyscallFailsWithErrno(ENOENT)); -} - -TEST_P(UdpSocketTest, GetSocketDetachFilter) { - int val = 0; - socklen_t val_len = sizeof(val); - ASSERT_THAT( - getsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len), - SyscallFailsWithErrno(ENOPROTOOPT)); -} - -TEST_P(UdpSocketTest, SendToZeroPort) { - char buf[8]; - struct sockaddr_storage addr = InetLoopbackAddr(); - - // Sending to an invalid port should fail. - SetPort(&addr, 0); - EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EINVAL)); - - SetPort(&addr, 1234); - EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallSucceedsWithValue(sizeof(buf))); -} - -TEST_P(UdpSocketTest, ConnectToZeroPortUnbound) { - struct sockaddr_storage addr = InetLoopbackAddr(); - SetPort(&addr, 0); - ASSERT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), - SyscallSucceeds()); -} - -TEST_P(UdpSocketTest, ConnectToZeroPortBound) { - struct sockaddr_storage addr = InetLoopbackAddr(); - ASSERT_NO_ERRNO( - BindSocket(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr))); - - SetPort(&addr, 0); - ASSERT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), - SyscallSucceeds()); - socklen_t len = sizeof(sockaddr_storage); - ASSERT_THAT( - getsockname(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), &len), - SyscallSucceeds()); - ASSERT_EQ(len, addrlen_); -} - -TEST_P(UdpSocketTest, ConnectToZeroPortConnected) { - struct sockaddr_storage addr = InetLoopbackAddr(); - ASSERT_NO_ERRNO( - BindSocket(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr))); - - // Connect to an address with non-zero port should succeed. - ASSERT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), - SyscallSucceeds()); - sockaddr_storage peername; - socklen_t peerlen = sizeof(peername); - ASSERT_THAT( - getpeername(sock_.get(), reinterpret_cast<struct sockaddr*>(&peername), - &peerlen), - SyscallSucceeds()); - ASSERT_EQ(peerlen, addrlen_); - ASSERT_EQ(memcmp(&peername, &addr, addrlen_), 0); - - // However connect() to an address with port 0 will make the following - // getpeername() fail. - SetPort(&addr, 0); - ASSERT_THAT( - connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), - SyscallSucceeds()); - ASSERT_THAT( - getpeername(sock_.get(), reinterpret_cast<struct sockaddr*>(&peername), - &peerlen), - SyscallFailsWithErrno(ENOTCONN)); -} - -INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest, - ::testing::Values(AddressFamily::kIpv4, - AddressFamily::kIpv6, - AddressFamily::kDualStack)); - -TEST(UdpInet6SocketTest, ConnectInet4Sockaddr) { - // glibc getaddrinfo expects the invariant expressed by this test to be held. - const sockaddr_in connect_sockaddr = { - .sin_family = AF_INET, .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}}; - auto sock_ = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)); - ASSERT_THAT( - connect(sock_.get(), - reinterpret_cast<const struct sockaddr*>(&connect_sockaddr), - sizeof(sockaddr_in)), - SyscallSucceeds()); - sockaddr_storage sockname; - socklen_t len = sizeof(sockaddr_storage); - ASSERT_THAT(getsockname(sock_.get(), - reinterpret_cast<struct sockaddr*>(&sockname), &len), - SyscallSucceeds()); - ASSERT_EQ(sockname.ss_family, AF_INET6); - ASSERT_EQ(len, sizeof(sockaddr_in6)); - auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&sockname); - char addr_buf[INET6_ADDRSTRLEN]; - const char* addr; - ASSERT_NE(addr = inet_ntop(sockname.ss_family, &sockname, addr_buf, - sizeof(addr_buf)), - nullptr); - ASSERT_TRUE(IN6_IS_ADDR_V4MAPPED(sin6->sin6_addr.s6_addr)) << addr; -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc deleted file mode 100644 index 4139a18d8..000000000 --- a/test/syscalls/linux/uidgid.cc +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <grp.h> -#include <sys/resource.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_join.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/uid_util.h" - -ABSL_FLAG(int32_t, scratch_uid1, 65534, "first scratch UID"); -ABSL_FLAG(int32_t, scratch_uid2, 65533, "second scratch UID"); -ABSL_FLAG(int32_t, scratch_gid1, 65534, "first scratch GID"); -ABSL_FLAG(int32_t, scratch_gid2, 65533, "second scratch GID"); - -// Force use of syscall instead of glibc set*id() wrappers because we want to -// apply to the current task only. libc sets all threads in a process because -// "POSIX requires that all threads in a process share the same credentials." -#define setuid USE_SYSCALL_INSTEAD -#define setgid USE_SYSCALL_INSTEAD -#define setreuid USE_SYSCALL_INSTEAD -#define setregid USE_SYSCALL_INSTEAD -#define setresuid USE_SYSCALL_INSTEAD -#define setresgid USE_SYSCALL_INSTEAD - -using ::testing::UnorderedElementsAreArray; - -namespace gvisor { -namespace testing { - -namespace { - -TEST(UidGidTest, Getuid) { - uid_t ruid, euid, suid; - EXPECT_THAT(getresuid(&ruid, &euid, &suid), SyscallSucceeds()); - EXPECT_THAT(getuid(), SyscallSucceedsWithValue(ruid)); - EXPECT_THAT(geteuid(), SyscallSucceedsWithValue(euid)); -} - -TEST(UidGidTest, Getgid) { - gid_t rgid, egid, sgid; - EXPECT_THAT(getresgid(&rgid, &egid, &sgid), SyscallSucceeds()); - EXPECT_THAT(getgid(), SyscallSucceedsWithValue(rgid)); - EXPECT_THAT(getegid(), SyscallSucceedsWithValue(egid)); -} - -TEST(UidGidTest, Getgroups) { - // "If size is zero, list is not modified, but the total number of - // supplementary group IDs for the process is returned." - getgroups(2) - int nr_groups; - ASSERT_THAT(nr_groups = getgroups(0, nullptr), SyscallSucceeds()); - std::vector<gid_t> list(nr_groups); - EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds()); - - // "EINVAL: size is less than the number of supplementary group IDs, but is - // not zero." - EXPECT_THAT(getgroups(-1, nullptr), SyscallFailsWithErrno(EINVAL)); - - // Testing for EFAULT requires actually having groups, which isn't guaranteed - // here; see the setgroups test below. -} - -// Checks that the calling process' real/effective/saved user IDs are -// ruid/euid/suid respectively. -PosixError CheckUIDs(uid_t ruid, uid_t euid, uid_t suid) { - uid_t actual_ruid, actual_euid, actual_suid; - int rc = getresuid(&actual_ruid, &actual_euid, &actual_suid); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "getresuid"); - } - if (ruid != actual_ruid || euid != actual_euid || suid != actual_suid) { - return PosixError( - EPERM, absl::StrCat( - "incorrect user IDs: got (", - absl::StrJoin({actual_ruid, actual_euid, actual_suid}, ", "), - ", wanted (", absl::StrJoin({ruid, euid, suid}, ", "), ")")); - } - return NoError(); -} - -PosixError CheckGIDs(gid_t rgid, gid_t egid, gid_t sgid) { - gid_t actual_rgid, actual_egid, actual_sgid; - int rc = getresgid(&actual_rgid, &actual_egid, &actual_sgid); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "getresgid"); - } - if (rgid != actual_rgid || egid != actual_egid || sgid != actual_sgid) { - return PosixError( - EPERM, absl::StrCat( - "incorrect group IDs: got (", - absl::StrJoin({actual_rgid, actual_egid, actual_sgid}, ", "), - ", wanted (", absl::StrJoin({rgid, egid, sgid}, ", "), ")")); - } - return NoError(); -} - -// N.B. These tests may break horribly unless run via a gVisor test runner, -// because changing UID in one test may forfeit permissions required by other -// tests. (The test runner runs each test in a separate process.) - -TEST(UidGidRootTest, Setuid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. After calling - // setuid(non-zero-UID), there is no way to get root privileges back. - ScopedThread([&] { - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. POSIX threads, however, require that all - // threads have the same UIDs, so using the setuid wrapper sets all threads' - // real UID. - EXPECT_THAT(syscall(SYS_setuid, -1), SyscallFailsWithErrno(EINVAL)); - - const uid_t uid = absl::GetFlag(FLAGS_scratch_uid1); - EXPECT_THAT(syscall(SYS_setuid, uid), SyscallSucceeds()); - // "If the effective UID of the caller is root (more precisely: if the - // caller has the CAP_SETUID capability), the real UID and saved set-user-ID - // are also set." - setuid(2) - EXPECT_NO_ERRNO(CheckUIDs(uid, uid, uid)); - }); -} - -TEST(UidGidRootTest, Setgid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - EXPECT_THAT(syscall(SYS_setgid, -1), SyscallFailsWithErrno(EINVAL)); - - ScopedThread([&] { - const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1); - EXPECT_THAT(syscall(SYS_setgid, gid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid)); - }); -} - -TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) { -#pragma push_macro("allow_setgid") -#undef setgid - - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - int old_gid = getgid(); - auto clean = Cleanup([old_gid] { setgid(old_gid); }); - - const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1); - // NOTE(b/64676707): Do setgid in a separate thread so that we can test if - // info.si_pid is set correctly. - ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); }); - EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid)); - -#pragma pop_macro("allow_setgid") -} - -TEST(UidGidRootTest, Setreuid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - // "Supplying a value of -1 for either the real or effective user ID forces - // the system to leave that ID unchanged." - setreuid(2) - EXPECT_THAT(syscall(SYS_setreuid, -1, -1), SyscallSucceeds()); - - EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0)); - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting - // this test. Otherwise, the files are created by root (UID before the - // test), but cannot be opened by the `uid` set below after the test. After - // calling setuid(non-zero-UID), there is no way to get root privileges - // back. - ScopedThread([&] { - const uid_t ruid = absl::GetFlag(FLAGS_scratch_uid1); - const uid_t euid = absl::GetFlag(FLAGS_scratch_uid2); - - EXPECT_THAT(syscall(SYS_setreuid, ruid, euid), SyscallSucceeds()); - - // "If the real user ID is set or the effective user ID is set to a value - // not equal to the previous real user ID, the saved set-user-ID will be - // set to the new effective user ID." - setreuid(2) - EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, euid)); - }); -} - -TEST(UidGidRootTest, Setregid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - EXPECT_THAT(syscall(SYS_setregid, -1, -1), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0)); - - ScopedThread([&] { - const gid_t rgid = absl::GetFlag(FLAGS_scratch_gid1); - const gid_t egid = absl::GetFlag(FLAGS_scratch_gid2); - ASSERT_THAT(syscall(SYS_setregid, rgid, egid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, egid)); - }); -} - -TEST(UidGidRootTest, Setresuid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - // "If one of the arguments equals -1, the corresponding value is not - // changed." - setresuid(2) - EXPECT_THAT(syscall(SYS_setresuid, -1, -1, -1), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0)); - - // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting - // this test. Otherwise, the files are created by root (UID before the - // test), but cannot be opened by the `uid` set below after the test. After - // calling setuid(non-zero-UID), there is no way to get root privileges - // back. - ScopedThread([&] { - const uid_t ruid = 12345; - const uid_t euid = 23456; - const uid_t suid = 34567; - - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. posix threads, however, require that - // all threads have the same UIDs, so using the setuid wrapper sets all - // threads' real UID. - EXPECT_THAT(syscall(SYS_setresuid, ruid, euid, suid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, suid)); - }); -} - -TEST(UidGidRootTest, Setresgid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - EXPECT_THAT(syscall(SYS_setresgid, -1, -1, -1), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0)); - - ScopedThread([&] { - const gid_t rgid = 12345; - const gid_t egid = 23456; - const gid_t sgid = 34567; - ASSERT_THAT(syscall(SYS_setresgid, rgid, egid, sgid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, sgid)); - }); -} - -TEST(UidGidRootTest, Setgroups) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - std::vector<gid_t> list = {123, 500}; - ASSERT_THAT(setgroups(list.size(), list.data()), SyscallSucceeds()); - std::vector<gid_t> list2(list.size()); - ASSERT_THAT(getgroups(list2.size(), list2.data()), SyscallSucceeds()); - EXPECT_THAT(list, UnorderedElementsAreArray(list2)); - - // "EFAULT: list has an invalid address." - EXPECT_THAT(getgroups(100, reinterpret_cast<gid_t*>(-1)), - SyscallFailsWithErrno(EFAULT)); -} - -TEST(UidGidRootTest, Setuid_prlimit) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - - // Do seteuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting - // this test. Otherwise, the files are created by root (UID before the - // test), but cannot be opened by the `uid` set below after the test. - ScopedThread([&] { - // Use syscall instead of glibc setuid wrapper because we want this - // seteuid call to only apply to this task. POSIX threads, however, - // require that all threads have the same UIDs, so using the seteuid - // wrapper sets all threads' UID. - EXPECT_THAT(syscall(SYS_setreuid, -1, 65534), SyscallSucceeds()); - - // Despite the UID change, we should be able to get our own limits. - struct rlimit rl = {}; - EXPECT_THAT(prlimit(0, RLIMIT_NOFILE, NULL, &rl), SyscallSucceeds()); - }); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/uname.cc b/test/syscalls/linux/uname.cc deleted file mode 100644 index d8824b171..000000000 --- a/test/syscalls/linux/uname.cc +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sched.h> -#include <sys/utsname.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/string_view.h" -#include "test/util/capability_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(UnameTest, Sanity) { - struct utsname buf; - ASSERT_THAT(uname(&buf), SyscallSucceeds()); - EXPECT_NE(strlen(buf.release), 0); - EXPECT_NE(strlen(buf.version), 0); - EXPECT_NE(strlen(buf.machine), 0); - EXPECT_NE(strlen(buf.sysname), 0); - EXPECT_NE(strlen(buf.nodename), 0); - EXPECT_NE(strlen(buf.domainname), 0); -} - -TEST(UnameTest, SetNames) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - char hostname[65]; - ASSERT_THAT(sethostname("0123456789", 3), SyscallSucceeds()); - EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(hostname), "012"); - - ASSERT_THAT(sethostname("0123456789\0xxx", 11), SyscallSucceeds()); - EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(hostname), "0123456789"); - - ASSERT_THAT(sethostname("0123456789\0xxx", 12), SyscallSucceeds()); - EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(hostname), "0123456789"); - - constexpr char kHostname[] = "wubbalubba"; - ASSERT_THAT(sethostname(kHostname, sizeof(kHostname)), SyscallSucceeds()); - - constexpr char kDomainname[] = "dubdub.com"; - ASSERT_THAT(setdomainname(kDomainname, sizeof(kDomainname)), - SyscallSucceeds()); - - struct utsname buf; - EXPECT_THAT(uname(&buf), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(buf.nodename), kHostname); - EXPECT_EQ(absl::string_view(buf.domainname), kDomainname); - - // These should just be glibc wrappers that also call uname(2). - EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(hostname), kHostname); - - char domainname[65]; - EXPECT_THAT(getdomainname(domainname, sizeof(domainname)), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(domainname), kDomainname); -} - -TEST(UnameTest, UnprivilegedSetNames) { - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) { - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false)); - } - - EXPECT_THAT(sethostname("", 0), SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(setdomainname("", 0), SyscallFailsWithErrno(EPERM)); -} - -TEST(UnameTest, UnshareUTS) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - struct utsname init; - ASSERT_THAT(uname(&init), SyscallSucceeds()); - - ScopedThread([&]() { - EXPECT_THAT(unshare(CLONE_NEWUTS), SyscallSucceeds()); - - constexpr char kHostname[] = "wubbalubba"; - EXPECT_THAT(sethostname(kHostname, sizeof(kHostname)), SyscallSucceeds()); - - char hostname[65]; - EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds()); - }); - - struct utsname after; - EXPECT_THAT(uname(&after), SyscallSucceeds()); - EXPECT_EQ(absl::string_view(after.nodename), init.nodename); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/unix_domain_socket_test_util.cc b/test/syscalls/linux/unix_domain_socket_test_util.cc deleted file mode 100644 index b05ab2900..000000000 --- a/test/syscalls/linux/unix_domain_socket_test_util.cc +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/unix_domain_socket_test_util.h" - -#include <sys/un.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -std::string DescribeUnixDomainSocketType(int type) { - const char* type_str = nullptr; - switch (type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC)) { - case SOCK_STREAM: - type_str = "SOCK_STREAM"; - break; - case SOCK_DGRAM: - type_str = "SOCK_DGRAM"; - break; - case SOCK_SEQPACKET: - type_str = "SOCK_SEQPACKET"; - break; - } - if (!type_str) { - return absl::StrCat("Unix domain socket with unknown type ", type); - } else { - return absl::StrCat(((type & SOCK_NONBLOCK) != 0) ? "non-blocking " : "", - ((type & SOCK_CLOEXEC) != 0) ? "close-on-exec " : "", - type_str, " Unix domain socket"); - } -} - -SocketPairKind UnixDomainSocketPair(int type) { - return SocketPairKind{DescribeUnixDomainSocketType(type), AF_UNIX, type, 0, - SyscallSocketPairCreator(AF_UNIX, type, 0)}; -} - -SocketPairKind FilesystemBoundUnixDomainSocketPair(int type) { - std::string description = absl::StrCat(DescribeUnixDomainSocketType(type), - " created with filesystem binding"); - if ((type & SOCK_DGRAM) == SOCK_DGRAM) { - return SocketPairKind{ - description, AF_UNIX, type, 0, - FilesystemBidirectionalBindSocketPairCreator(AF_UNIX, type, 0)}; - } - return SocketPairKind{ - description, AF_UNIX, type, 0, - FilesystemAcceptBindSocketPairCreator(AF_UNIX, type, 0)}; -} - -SocketPairKind AbstractBoundUnixDomainSocketPair(int type) { - std::string description = - absl::StrCat(DescribeUnixDomainSocketType(type), - " created with abstract namespace binding"); - if ((type & SOCK_DGRAM) == SOCK_DGRAM) { - return SocketPairKind{ - description, AF_UNIX, type, 0, - AbstractBidirectionalBindSocketPairCreator(AF_UNIX, type, 0)}; - } - return SocketPairKind{description, AF_UNIX, type, 0, - AbstractAcceptBindSocketPairCreator(AF_UNIX, type, 0)}; -} - -SocketPairKind SocketpairGoferUnixDomainSocketPair(int type) { - std::string description = absl::StrCat(DescribeUnixDomainSocketType(type), - " created with the socketpair gofer"); - return SocketPairKind{description, AF_UNIX, type, 0, - SocketpairGoferSocketPairCreator(AF_UNIX, type, 0)}; -} - -SocketPairKind SocketpairGoferFileSocketPair(int type) { - std::string description = - absl::StrCat(((type & O_NONBLOCK) != 0) ? "non-blocking " : "", - ((type & O_CLOEXEC) != 0) ? "close-on-exec " : "", - "file socket created with the socketpair gofer"); - // The socketpair gofer always creates SOCK_STREAM sockets on open(2). - return SocketPairKind{description, AF_UNIX, SOCK_STREAM, 0, - SocketpairGoferFileSocketPairCreator(type)}; -} - -SocketPairKind FilesystemUnboundUnixDomainSocketPair(int type) { - return SocketPairKind{absl::StrCat(DescribeUnixDomainSocketType(type), - " unbound with a filesystem address"), - AF_UNIX, type, 0, - FilesystemUnboundSocketPairCreator(AF_UNIX, type, 0)}; -} - -SocketPairKind AbstractUnboundUnixDomainSocketPair(int type) { - return SocketPairKind{ - absl::StrCat(DescribeUnixDomainSocketType(type), - " unbound with an abstract namespace address"), - AF_UNIX, type, 0, AbstractUnboundSocketPairCreator(AF_UNIX, type, 0)}; -} - -void SendSingleFD(int sock, int fd, char buf[], int buf_size) { - ASSERT_NO_FATAL_FAILURE(SendFDs(sock, &fd, 1, buf, buf_size)); -} - -void SendFDs(int sock, int fds[], int fds_size, char buf[], int buf_size) { - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(fds_size * sizeof(int))); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(fds_size * sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - for (int i = 0; i < fds_size; i++) { - memcpy(CMSG_DATA(cmsg) + i * sizeof(int), &fds[i], sizeof(int)); - } - - ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size), - IsPosixErrorOkAndHolds(buf_size)); -} - -void RecvSingleFD(int sock, int* fd, char buf[], int buf_size) { - ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, buf_size)); -} - -void RecvSingleFD(int sock, int* fd, char buf[], int buf_size, - int expected_size) { - ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, expected_size)); -} - -void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size) { - ASSERT_NO_FATAL_FAILURE( - RecvFDs(sock, fds, fds_size, buf, buf_size, buf_size)); -} - -void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size, - int expected_size, bool peek) { - struct msghdr msg = {}; - std::vector<char> control(CMSG_SPACE(fds_size * sizeof(int))); - msg.msg_control = &control[0]; - msg.msg_controllen = control.size(); - - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - int flags = 0; - if (peek) { - flags |= MSG_PEEK; - } - - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, flags), - SyscallSucceedsWithValue(expected_size)); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(fds_size * sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS); - - for (int i = 0; i < fds_size; i++) { - memcpy(&fds[i], CMSG_DATA(cmsg) + i * sizeof(int), sizeof(int)); - } -} - -void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size, - int expected_size) { - ASSERT_NO_FATAL_FAILURE( - RecvFDs(sock, fds, fds_size, buf, buf_size, expected_size, false)); -} - -void PeekSingleFD(int sock, int* fd, char buf[], int buf_size) { - ASSERT_NO_FATAL_FAILURE(RecvFDs(sock, fd, 1, buf, buf_size, buf_size, true)); -} - -void RecvNoCmsg(int sock, char buf[], int buf_size, int expected_size) { - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0), - SyscallSucceedsWithValue(expected_size)); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - EXPECT_EQ(cmsg, nullptr); -} - -void SendNullCmsg(int sock, char buf[], int buf_size) { - struct msghdr msg = {}; - msg.msg_control = nullptr; - msg.msg_controllen = 0; - - ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size), - IsPosixErrorOkAndHolds(buf_size)); -} - -void SendCreds(int sock, ucred creds, char buf[], int buf_size) { - struct msghdr msg = {}; - - char control[CMSG_SPACE(sizeof(struct ucred))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - memcpy(CMSG_DATA(cmsg), &creds, sizeof(struct ucred)); - - ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size), - IsPosixErrorOkAndHolds(buf_size)); -} - -void SendCredsAndFD(int sock, ucred creds, int fd, char buf[], int buf_size) { - struct msghdr msg = {}; - - char control[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))] = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct cmsghdr* cmsg1 = CMSG_FIRSTHDR(&msg); - cmsg1->cmsg_level = SOL_SOCKET; - cmsg1->cmsg_type = SCM_CREDENTIALS; - cmsg1->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - memcpy(CMSG_DATA(cmsg1), &creds, sizeof(struct ucred)); - - struct cmsghdr* cmsg2 = CMSG_NXTHDR(&msg, cmsg1); - cmsg2->cmsg_level = SOL_SOCKET; - cmsg2->cmsg_type = SCM_RIGHTS; - cmsg2->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cmsg2), &fd, sizeof(int)); - - ASSERT_THAT(SendMsg(sock, &msg, buf, buf_size), - IsPosixErrorOkAndHolds(buf_size)); -} - -void RecvCreds(int sock, ucred* creds, char buf[], int buf_size) { - ASSERT_NO_FATAL_FAILURE(RecvCreds(sock, creds, buf, buf_size, buf_size)); -} - -void RecvCreds(int sock, ucred* creds, char buf[], int buf_size, - int expected_size) { - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(struct ucred))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0), - SyscallSucceedsWithValue(expected_size)); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct ucred))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); - - memcpy(creds, CMSG_DATA(cmsg), sizeof(struct ucred)); -} - -void RecvCredsAndFD(int sock, ucred* creds, int* fd, char buf[], int buf_size) { - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0), - SyscallSucceedsWithValue(buf_size)); - - struct cmsghdr* cmsg1 = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg1, nullptr); - ASSERT_EQ(cmsg1->cmsg_len, CMSG_LEN(sizeof(struct ucred))); - ASSERT_EQ(cmsg1->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg1->cmsg_type, SCM_CREDENTIALS); - memcpy(creds, CMSG_DATA(cmsg1), sizeof(struct ucred)); - - struct cmsghdr* cmsg2 = CMSG_NXTHDR(&msg, cmsg1); - ASSERT_NE(cmsg2, nullptr); - ASSERT_EQ(cmsg2->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg2->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg2->cmsg_type, SCM_RIGHTS); - memcpy(fd, CMSG_DATA(cmsg2), sizeof(int)); -} - -void RecvSingleFDUnaligned(int sock, int* fd, char buf[], int buf_size) { - struct msghdr msg = {}; - char control[CMSG_SPACE(sizeof(int)) - sizeof(int)]; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = buf_size; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(recvmsg)(sock, &msg, 0), - SyscallSucceedsWithValue(buf_size)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS); - - memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); -} - -void SetSoPassCred(int sock) { - int one = 1; - EXPECT_THAT(setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)), - SyscallSucceeds()); -} - -void UnsetSoPassCred(int sock) { - int zero = 0; - EXPECT_THAT(setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &zero, sizeof(zero)), - SyscallSucceeds()); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/unix_domain_socket_test_util.h b/test/syscalls/linux/unix_domain_socket_test_util.h deleted file mode 100644 index b8073db17..000000000 --- a/test/syscalls/linux/unix_domain_socket_test_util.h +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_ - -#include <string> - -#include "test/syscalls/linux/socket_test_util.h" - -namespace gvisor { -namespace testing { - -// DescribeUnixDomainSocketType returns a human-readable string explaining the -// given Unix domain socket type. -std::string DescribeUnixDomainSocketType(int type); - -// UnixDomainSocketPair returns a SocketPairKind that represents SocketPairs -// created by invoking the socketpair() syscall with AF_UNIX and the given type. -SocketPairKind UnixDomainSocketPair(int type); - -// FilesystemBoundUnixDomainSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and accept() syscalls with a temp file path, -// AF_UNIX and the given type. -SocketPairKind FilesystemBoundUnixDomainSocketPair(int type); - -// AbstractBoundUnixDomainSocketPair returns a SocketPairKind that represents -// SocketPairs created with bind() and accept() syscalls with a temp abstract -// path, AF_UNIX and the given type. -SocketPairKind AbstractBoundUnixDomainSocketPair(int type); - -// SocketpairGoferUnixDomainSocketPair returns a SocketPairKind that was created -// with two sockets connected to the socketpair gofer. -SocketPairKind SocketpairGoferUnixDomainSocketPair(int type); - -// SocketpairGoferFileSocketPair returns a SocketPairKind that was created with -// two open() calls on paths backed by the socketpair gofer. -SocketPairKind SocketpairGoferFileSocketPair(int type); - -// FilesystemUnboundUnixDomainSocketPair returns a SocketPairKind that -// represents two unbound sockets and a filesystem path for binding. -SocketPairKind FilesystemUnboundUnixDomainSocketPair(int type); - -// AbstractUnboundUnixDomainSocketPair returns a SocketPairKind that represents -// two unbound sockets and an abstract namespace path for binding. -SocketPairKind AbstractUnboundUnixDomainSocketPair(int type); - -// SendSingleFD sends both a single FD and some data over a unix domain socket -// specified by an FD. Note that calls to this function must be wrapped in -// ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void SendSingleFD(int sock, int fd, char buf[], int buf_size); - -// SendFDs sends an arbitrary number of FDs and some data over a unix domain -// socket specified by an FD. Note that calls to this function must be wrapped -// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void SendFDs(int sock, int fds[], int fds_size, char buf[], int buf_size); - -// RecvSingleFD receives both a single FD and some data over a unix domain -// socket specified by an FD. Note that calls to this function must be wrapped -// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void RecvSingleFD(int sock, int* fd, char buf[], int buf_size); - -// RecvSingleFD receives both a single FD and some data over a unix domain -// socket specified by an FD. This version allows the expected amount of data -// received to be different than the buffer size. Note that calls to this -// function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions -// to halt the test. -void RecvSingleFD(int sock, int* fd, char buf[], int buf_size, - int expected_size); - -// PeekSingleFD peeks at both a single FD and some data over a unix domain -// socket specified by an FD. Note that calls to this function must be wrapped -// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void PeekSingleFD(int sock, int* fd, char buf[], int buf_size); - -// RecvFDs receives both an arbitrary number of FDs and some data over a unix -// domain socket specified by an FD. Note that calls to this function must be -// wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size); - -// RecvFDs receives both an arbitrary number of FDs and some data over a unix -// domain socket specified by an FD. This version allows the expected amount of -// data received to be different than the buffer size. Note that calls to this -// function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions -// to halt the test. -void RecvFDs(int sock, int fds[], int fds_size, char buf[], int buf_size, - int expected_size); - -// RecvNoCmsg receives some data over a unix domain socket specified by an FD -// and asserts that no control messages are available for receiving. Note that -// calls to this function must be wrapped in ASSERT_NO_FATAL_FAILURE for -// internal assertions to halt the test. -void RecvNoCmsg(int sock, char buf[], int buf_size, int expected_size); - -inline void RecvNoCmsg(int sock, char buf[], int buf_size) { - RecvNoCmsg(sock, buf, buf_size, buf_size); -} - -// SendCreds sends the credentials of the current process and some data over a -// unix domain socket specified by an FD. Note that calls to this function must -// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the -// test. -void SendCreds(int sock, ucred creds, char buf[], int buf_size); - -// SendCredsAndFD sends the credentials of the current process, a single FD, and -// some data over a unix domain socket specified by an FD. Note that calls to -// this function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal -// assertions to halt the test. -void SendCredsAndFD(int sock, ucred creds, int fd, char buf[], int buf_size); - -// RecvCreds receives some credentials and some data over a unix domain socket -// specified by an FD. Note that calls to this function must be wrapped in -// ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void RecvCreds(int sock, ucred* creds, char buf[], int buf_size); - -// RecvCreds receives some credentials and some data over a unix domain socket -// specified by an FD. This version allows the expected amount of data received -// to be different than the buffer size. Note that calls to this function must -// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the -// test. -void RecvCreds(int sock, ucred* creds, char buf[], int buf_size, - int expected_size); - -// RecvCredsAndFD receives some credentials, a single FD, and some data over a -// unix domain socket specified by an FD. Note that calls to this function must -// be wrapped in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the -// test. -void RecvCredsAndFD(int sock, ucred* creds, int* fd, char buf[], int buf_size); - -// SendNullCmsg sends a null control message and some data over a unix domain -// socket specified by an FD. Note that calls to this function must be wrapped -// in ASSERT_NO_FATAL_FAILURE for internal assertions to halt the test. -void SendNullCmsg(int sock, char buf[], int buf_size); - -// RecvSingleFDUnaligned sends both a single FD and some data over a unix domain -// socket specified by an FD. This function does not obey the spec, but Linux -// allows it and the apphosting code depends on this quirk. Note that calls to -// this function must be wrapped in ASSERT_NO_FATAL_FAILURE for internal -// assertions to halt the test. -void RecvSingleFDUnaligned(int sock, int* fd, char buf[], int buf_size); - -// SetSoPassCred sets the SO_PASSCRED option on the specified socket. -void SetSoPassCred(int sock); - -// UnsetSoPassCred clears the SO_PASSCRED option on the specified socket. -void UnsetSoPassCred(int sock); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_UNIX_DOMAIN_SOCKET_TEST_UTIL_H_ diff --git a/test/syscalls/linux/unlink.cc b/test/syscalls/linux/unlink.cc deleted file mode 100644 index 061e2e0f1..000000000 --- a/test/syscalls/linux/unlink.cc +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <unistd.h> - -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(UnlinkTest, IsDir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - EXPECT_THAT(unlink(dir.path().c_str()), SyscallFailsWithErrno(EISDIR)); -} - -TEST(UnlinkTest, DirNotEmpty) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - int fd; - std::string path = JoinPath(dir.path(), "ExistingFile"); - EXPECT_THAT(fd = open(path.c_str(), O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - EXPECT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(ENOTEMPTY)); -} - -TEST(UnlinkTest, Rmdir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(rmdir(dir.path().c_str()), SyscallSucceeds()); -} - -TEST(UnlinkTest, AtDir) { - int dirfd; - auto tmpdir = GetAbsoluteTestTmpdir(); - EXPECT_THAT(dirfd = open(tmpdir.c_str(), O_DIRECTORY, 0), SyscallSucceeds()); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(tmpdir)); - auto dir_relpath = - ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(tmpdir, dir.path())); - EXPECT_THAT(unlinkat(dirfd, dir_relpath.c_str(), AT_REMOVEDIR), - SyscallSucceeds()); - ASSERT_THAT(close(dirfd), SyscallSucceeds()); -} - -TEST(UnlinkTest, AtDirDegradedPermissions_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - int dirfd; - ASSERT_THAT(dirfd = open(dir.path().c_str(), O_DIRECTORY, 0), - SyscallSucceeds()); - - std::string sub_dir = JoinPath(dir.path(), "NewDir"); - EXPECT_THAT(mkdir(sub_dir.c_str(), 0755), SyscallSucceeds()); - EXPECT_THAT(fchmod(dirfd, 0444), SyscallSucceeds()); - EXPECT_THAT(unlinkat(dirfd, "NewDir", AT_REMOVEDIR), - SyscallFailsWithErrno(EACCES)); - ASSERT_THAT(close(dirfd), SyscallSucceeds()); -} - -// Files cannot be unlinked if the parent is not writable and executable. -TEST(UnlinkTest, ParentDegradedPermissions) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - - ASSERT_THAT(chmod(dir.path().c_str(), 0000), SyscallSucceeds()); - - struct stat st; - ASSERT_THAT(stat(file.path().c_str(), &st), SyscallFailsWithErrno(EACCES)); - ASSERT_THAT(unlinkat(AT_FDCWD, file.path().c_str(), 0), - SyscallFailsWithErrno(EACCES)); - - // Non-existent files also return EACCES. - const std::string nonexist = JoinPath(dir.path(), "doesnotexist"); - ASSERT_THAT(stat(nonexist.c_str(), &st), SyscallFailsWithErrno(EACCES)); - ASSERT_THAT(unlinkat(AT_FDCWD, nonexist.c_str(), 0), - SyscallFailsWithErrno(EACCES)); -} - -TEST(UnlinkTest, AtBad) { - int dirfd; - EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0), - SyscallSucceeds()); - - // Try removing a directory as a file. - std::string path = JoinPath(GetAbsoluteTestTmpdir(), "NewDir"); - EXPECT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds()); - EXPECT_THAT(unlinkat(dirfd, "NewDir", 0), SyscallFailsWithErrno(EISDIR)); - EXPECT_THAT(unlinkat(dirfd, "NewDir", AT_REMOVEDIR), SyscallSucceeds()); - - // Try removing a file as a directory. - int fd; - EXPECT_THAT(fd = openat(dirfd, "UnlinkAtFile", O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", AT_REMOVEDIR), - SyscallFailsWithErrno(ENOTDIR)); - EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile/", 0), - SyscallFailsWithErrno(ENOTDIR)); - ASSERT_THAT(close(fd), SyscallSucceeds()); - EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", 0), SyscallSucceeds()); - - // Cleanup. - ASSERT_THAT(close(dirfd), SyscallSucceeds()); -} - -TEST(UnlinkTest, AbsTmpFile) { - int fd; - std::string path = JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile"); - EXPECT_THAT(fd = open(path.c_str(), O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - EXPECT_THAT(unlink(path.c_str()), SyscallSucceeds()); -} - -TEST(UnlinkTest, TooLongName) { - EXPECT_THAT(unlink(std::vector<char>(16384, '0').data()), - SyscallFailsWithErrno(ENAMETOOLONG)); -} - -TEST(UnlinkTest, BadNamePtr) { - EXPECT_THAT(unlink(reinterpret_cast<char*>(1)), - SyscallFailsWithErrno(EFAULT)); -} - -TEST(UnlinkTest, AtFile) { - int dirfd; - EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0666), - SyscallSucceeds()); - int fd; - EXPECT_THAT(fd = openat(dirfd, "UnlinkAtFile", O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - EXPECT_THAT(unlinkat(dirfd, "UnlinkAtFile", 0), SyscallSucceeds()); -} - -TEST(UnlinkTest, OpenFile_NoRandomSave) { - // We can't save unlinked file unless they are on tmpfs. - const DisableSave ds; - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - int fd; - EXPECT_THAT(fd = open(file.path().c_str(), O_RDWR, 0666), SyscallSucceeds()); - EXPECT_THAT(unlink(file.path().c_str()), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST(UnlinkTest, CannotRemoveDots) { - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const std::string self = JoinPath(file.path(), "."); - ASSERT_THAT(unlink(self.c_str()), SyscallFailsWithErrno(ENOTDIR)); - const std::string parent = JoinPath(file.path(), ".."); - ASSERT_THAT(unlink(parent.c_str()), SyscallFailsWithErrno(ENOTDIR)); -} - -TEST(UnlinkTest, CannotRemoveRoot) { - ASSERT_THAT(unlinkat(-1, "/", AT_REMOVEDIR), SyscallFailsWithErrno(EBUSY)); -} - -TEST(UnlinkTest, CannotRemoveRootWithAtDir) { - const FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( - Open(GetAbsoluteTestTmpdir(), O_DIRECTORY, 0666)); - ASSERT_THAT(unlinkat(dirfd.get(), "/", AT_REMOVEDIR), - SyscallFailsWithErrno(EBUSY)); -} - -TEST(RmdirTest, CannotRemoveDots) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string self = JoinPath(dir.path(), "."); - ASSERT_THAT(rmdir(self.c_str()), SyscallFailsWithErrno(EINVAL)); - const std::string parent = JoinPath(dir.path(), ".."); - ASSERT_THAT(rmdir(parent.c_str()), SyscallFailsWithErrno(ENOTEMPTY)); -} - -TEST(RmdirTest, CanRemoveWithTrailingSlashes) { - auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string slash = absl::StrCat(dir1.path(), "/"); - ASSERT_THAT(rmdir(slash.c_str()), SyscallSucceeds()); - auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string slashslash = absl::StrCat(dir2.path(), "//"); - ASSERT_THAT(rmdir(slashslash.c_str()), SyscallSucceeds()); -} - -TEST(UnlinkTest, UnlinkAtEmptyPath) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); - auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666)); - EXPECT_THAT(unlinkat(fd.get(), "", 0), SyscallFailsWithErrno(ENOENT)); - - auto dirInDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); - auto dirFD = ASSERT_NO_ERRNO_AND_VALUE( - Open(dirInDir.path(), O_RDONLY | O_DIRECTORY, 0666)); - EXPECT_THAT(unlinkat(dirFD.get(), "", AT_REMOVEDIR), - SyscallFailsWithErrno(ENOENT)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/unshare.cc b/test/syscalls/linux/unshare.cc deleted file mode 100644 index e32619efe..000000000 --- a/test/syscalls/linux/unshare.cc +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sched.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/synchronization/mutex.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(UnshareTest, AllowsZeroFlags) { - ASSERT_THAT(unshare(0), SyscallSucceeds()); -} - -TEST(UnshareTest, ThreadFlagFailsIfMultithreaded) { - absl::Mutex mu; - bool finished = false; - ScopedThread t([&] { - mu.Lock(); - mu.Await(absl::Condition(&finished)); - mu.Unlock(); - }); - ASSERT_THAT(unshare(CLONE_THREAD), SyscallFailsWithErrno(EINVAL)); - mu.Lock(); - finished = true; - mu.Unlock(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/utimes.cc b/test/syscalls/linux/utimes.cc deleted file mode 100644 index e647d2896..000000000 --- a/test/syscalls/linux/utimes.cc +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> -#include <utime.h> - -#include <string> - -#include "absl/time/time.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// TimeBoxed runs fn, setting before and after to (coarse realtime) times -// guaranteed* to come before and after fn started and completed, respectively. -// -// fn may be called more than once if the clock is adjusted. -void TimeBoxed(absl::Time* before, absl::Time* after, - std::function<void()> const& fn) { - do { - // N.B. utimes and friends use CLOCK_REALTIME_COARSE for setting time (i.e., - // current_kernel_time()). See fs/attr.c:notify_change. - // - // notify_change truncates the time to a multiple of s_time_gran, but most - // filesystems set it to 1, so we don't do any truncation. - struct timespec ts; - EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds()); - // FIXME(b/132819225): gVisor filesystem timestamps inconsistently use the - // internal or host clock, which may diverge slightly. Allow some slack on - // times to account for the difference. - *before = absl::TimeFromTimespec(ts) - absl::Seconds(1); - - fn(); - - EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds()); - *after = absl::TimeFromTimespec(ts) + absl::Seconds(1); - - if (*after < *before) { - // Clock jumped backwards; retry. - // - // Technically this misses jumps small enough to keep after > before, - // which could lead to test failures, but that is very unlikely to happen. - continue; - } - } while (*after < *before); -} - -void TestUtimesOnPath(std::string const& path) { - struct stat statbuf; - - struct timeval times[2] = {{10, 0}, {20, 0}}; - EXPECT_THAT(utimes(path.c_str(), times), SyscallSucceeds()); - EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds()); - EXPECT_EQ(10, statbuf.st_atime); - EXPECT_EQ(20, statbuf.st_mtime); - - absl::Time before; - absl::Time after; - TimeBoxed(&before, &after, [&] { - EXPECT_THAT(utimes(path.c_str(), nullptr), SyscallSucceeds()); - }); - - EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds()); - - absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim); - EXPECT_GE(atime, before); - EXPECT_LE(atime, after); - - absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); - EXPECT_GE(mtime, before); - EXPECT_LE(mtime, after); -} - -TEST(UtimesTest, OnFile) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - TestUtimesOnPath(f.path()); -} - -TEST(UtimesTest, OnDir) { - auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TestUtimesOnPath(dir.path()); -} - -TEST(UtimesTest, MissingPath) { - auto path = NewTempAbsPath(); - struct timeval times[2] = {{10, 0}, {20, 0}}; - EXPECT_THAT(utimes(path.c_str(), times), SyscallFailsWithErrno(ENOENT)); -} - -void TestFutimesat(int dirFd, std::string const& path) { - struct stat statbuf; - - struct timeval times[2] = {{10, 0}, {20, 0}}; - EXPECT_THAT(futimesat(dirFd, path.c_str(), times), SyscallSucceeds()); - EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); - EXPECT_EQ(10, statbuf.st_atime); - EXPECT_EQ(20, statbuf.st_mtime); - - absl::Time before; - absl::Time after; - TimeBoxed(&before, &after, [&] { - EXPECT_THAT(futimesat(dirFd, path.c_str(), nullptr), SyscallSucceeds()); - }); - - EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); - - absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim); - EXPECT_GE(atime, before); - EXPECT_LE(atime, after); - - absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); - EXPECT_GE(mtime, before); - EXPECT_LE(mtime, after); -} - -TEST(FutimesatTest, OnAbsPath) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - TestFutimesat(0, f.path()); -} - -TEST(FutimesatTest, OnRelPath) { - auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path())); - auto basename = std::string(Basename(f.path())); - const FileDescriptor dirFd = - ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY)); - TestFutimesat(dirFd.get(), basename); -} - -TEST(FutimesatTest, InvalidNsec) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - struct timeval times[4][2] = {{ - {0, 1}, // Valid - {1, static_cast<int64_t>(1e7)} // Invalid - }, - { - {1, static_cast<int64_t>(1e7)}, // Invalid - {0, 1} // Valid - }, - { - {0, 1}, // Valid - {1, -1} // Invalid - }, - { - {1, -1}, // Invalid - {0, 1} // Valid - }}; - - for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) { - std::cout << "test:" << i << "\n"; - EXPECT_THAT(futimesat(0, f.path().c_str(), times[i]), - SyscallFailsWithErrno(EINVAL)); - } -} - -void TestUtimensat(int dirFd, std::string const& path) { - struct stat statbuf; - const struct timespec times[2] = {{10, 0}, {20, 0}}; - EXPECT_THAT(utimensat(dirFd, path.c_str(), times, 0), SyscallSucceeds()); - EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); - EXPECT_EQ(10, statbuf.st_atime); - EXPECT_EQ(20, statbuf.st_mtime); - - // Test setting with UTIME_NOW and UTIME_OMIT. - struct stat statbuf2; - const struct timespec times2[2] = { - {0, UTIME_NOW}, // Should set atime to now. - {0, UTIME_OMIT} // Should not change mtime. - }; - - absl::Time before; - absl::Time after; - TimeBoxed(&before, &after, [&] { - EXPECT_THAT(utimensat(dirFd, path.c_str(), times2, 0), SyscallSucceeds()); - }); - - EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf2, 0), SyscallSucceeds()); - - absl::Time atime2 = absl::TimeFromTimespec(statbuf2.st_atim); - EXPECT_GE(atime2, before); - EXPECT_LE(atime2, after); - - absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); - absl::Time mtime2 = absl::TimeFromTimespec(statbuf2.st_mtim); - // mtime should not be changed. - EXPECT_EQ(mtime, mtime2); - - // Test setting with times = NULL. Should set both atime and mtime to the - // current system time. - struct stat statbuf3; - TimeBoxed(&before, &after, [&] { - EXPECT_THAT(utimensat(dirFd, path.c_str(), nullptr, 0), SyscallSucceeds()); - }); - - EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf3, 0), SyscallSucceeds()); - - absl::Time atime3 = absl::TimeFromTimespec(statbuf3.st_atim); - EXPECT_GE(atime3, before); - EXPECT_LE(atime3, after); - - absl::Time mtime3 = absl::TimeFromTimespec(statbuf3.st_mtim); - EXPECT_GE(mtime3, before); - EXPECT_LE(mtime3, after); - - EXPECT_EQ(atime3, mtime3); -} - -TEST(UtimensatTest, OnAbsPath) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - TestUtimensat(0, f.path()); -} - -TEST(UtimensatTest, OnRelPath) { - auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path())); - auto basename = std::string(Basename(f.path())); - const FileDescriptor dirFd = - ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY)); - TestUtimensat(dirFd.get(), basename); -} - -TEST(UtimensatTest, OmitNoop) { - // Setting both timespecs to UTIME_OMIT on a nonexistant path should succeed. - auto path = NewTempAbsPath(); - const struct timespec times[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}}; - EXPECT_THAT(utimensat(0, path.c_str(), times, 0), SyscallSucceeds()); -} - -// Verify that we can actually set atime and mtime to 0. -TEST(UtimeTest, ZeroAtimeandMtime) { - const auto tmp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const auto tmp_file = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_dir.path())); - - // Stat the file before and after updating atime and mtime. - struct stat stat_before = {}; - EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_before), SyscallSucceeds()); - - ASSERT_NE(stat_before.st_atime, 0); - ASSERT_NE(stat_before.st_mtime, 0); - - const struct utimbuf times = {}; // Zero for both atime and mtime. - EXPECT_THAT(utime(tmp_file.path().c_str(), ×), SyscallSucceeds()); - - struct stat stat_after = {}; - EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_after), SyscallSucceeds()); - - // We should see the atime and mtime changed when we set them to 0. - ASSERT_EQ(stat_after.st_atime, 0); - ASSERT_EQ(stat_after.st_mtime, 0); -} - -TEST(UtimensatTest, InvalidNsec) { - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - struct timespec times[2][2] = { - { - {0, UTIME_OMIT}, // Valid - {2, static_cast<int64_t>(1e10)} // Invalid - }, - { - {2, static_cast<int64_t>(1e10)}, // Invalid - {0, UTIME_OMIT} // Valid - }}; - - for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) { - std::cout << "test:" << i << "\n"; - EXPECT_THAT(utimensat(0, f.path().c_str(), times[i], 0), - SyscallFailsWithErrno(EINVAL)); - } -} - -TEST(Utimensat, NullPath) { - // From man utimensat(2): - // "the Linux utimensat() system call implements a nonstandard feature: if - // pathname is NULL, then the call modifies the timestamps of the file - // referred to by the file descriptor dirfd (which may refer to any type of - // file). - // Note, however, that the glibc wrapper for utimensat() disallows - // passing NULL as the value for file: the wrapper function returns the error - // EINVAL in this case." - auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); - struct stat statbuf; - const struct timespec times[2] = {{10, 0}, {20, 0}}; - // Call syscall directly. - EXPECT_THAT(syscall(SYS_utimensat, fd.get(), NULL, times, 0), - SyscallSucceeds()); - EXPECT_THAT(fstatat(0, f.path().c_str(), &statbuf, 0), SyscallSucceeds()); - EXPECT_EQ(10, statbuf.st_atime); - EXPECT_EQ(20, statbuf.st_mtime); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/vdso.cc b/test/syscalls/linux/vdso.cc deleted file mode 100644 index 19c80add8..000000000 --- a/test/syscalls/linux/vdso.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <string.h> -#include <sys/mman.h> - -#include <algorithm> - -#include "gtest/gtest.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/proc_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// Ensure that the vvar page cannot be made writable. -TEST(VvarTest, WriteVvar) { - auto contents = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps")); - auto maps = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(contents)); - auto it = std::find_if(maps.begin(), maps.end(), [](const ProcMapsEntry& e) { - return e.filename == "[vvar]"; - }); - - SKIP_IF(it == maps.end()); - EXPECT_THAT(mprotect(reinterpret_cast<void*>(it->start), kPageSize, - PROT_READ | PROT_WRITE), - SyscallFailsWithErrno(EACCES)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/vdso_clock_gettime.cc b/test/syscalls/linux/vdso_clock_gettime.cc deleted file mode 100644 index 2a8699a7b..000000000 --- a/test/syscalls/linux/vdso_clock_gettime.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <stdint.h> -#include <sys/time.h> -#include <syscall.h> -#include <time.h> -#include <unistd.h> - -#include <map> -#include <string> -#include <utility> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/numbers.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { - switch (info.param) { - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - default: - return absl::StrCat(info.param); - } -} - -class MonotonicVDSOClockTest : public ::testing::TestWithParam<clockid_t> {}; - -TEST_P(MonotonicVDSOClockTest, IsCorrect) { - // The VDSO implementation of clock_gettime() uses the TSC. On KVM, sentry and - // application TSCs can be very desynchronized; see - // sentry/platform/kvm/kvm.vCPU.setSystemTime(). - SKIP_IF(GvisorPlatform() == Platform::kKVM); - - // Check that when we alternate readings from the clock_gettime syscall and - // the VDSO's implementation, we observe the combined sequence as being - // monotonic. - struct timespec tvdso, tsys; - absl::Time vdso_time, sys_time; - ASSERT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys), - SyscallSucceeds()); - sys_time = absl::TimeFromTimespec(tsys); - auto end = absl::Now() + absl::Seconds(10); - while (absl::Now() < end) { - ASSERT_THAT(clock_gettime(GetParam(), &tvdso), SyscallSucceeds()); - vdso_time = absl::TimeFromTimespec(tvdso); - EXPECT_LE(sys_time, vdso_time); - ASSERT_THAT(syscall(__NR_clock_gettime, GetParam(), &tsys), - SyscallSucceeds()); - sys_time = absl::TimeFromTimespec(tsys); - EXPECT_LE(vdso_time, sys_time); - } -} - -INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicVDSOClockTest, - ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME), - PrintClockId); - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/vfork.cc b/test/syscalls/linux/vfork.cc deleted file mode 100644 index 19d05998e..000000000 --- a/test/syscalls/linux/vfork.cc +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <string> -#include <utility> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/time/time.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/test_util.h" -#include "test/util/time_util.h" - -ABSL_FLAG(bool, vfork_test_child, false, - "If true, run the VforkTest child workload."); - -namespace gvisor { -namespace testing { - -namespace { - -// We don't test with raw CLONE_VFORK to avoid interacting with glibc's use of -// TLS. -// -// Even with vfork(2), we must be careful to do little more in the child than -// call execve(2). We use the simplest sleep function possible, though this is -// still precarious, as we're officially only allowed to call execve(2) and -// _exit(2). -constexpr absl::Duration kChildDelay = absl::Seconds(10); - -// Exit code for successful child subprocesses. We don't want to use 0 since -// it's too common, and an execve(2) failure causes the child to exit with the -// errno, so kChildExitCode is chosen to be an unlikely errno: -constexpr int kChildExitCode = 118; // ENOTNAM: Not a XENIX named type file - -int64_t MonotonicNow() { - struct timespec now; - TEST_PCHECK(clock_gettime(CLOCK_MONOTONIC, &now) == 0); - return now.tv_sec * 1000000000ll + now.tv_nsec; -} - -TEST(VforkTest, ParentStopsUntilChildExits) { - const auto test = [] { - // N.B. Run the test in a single-threaded subprocess because - // vfork is not safe in a multi-threaded process. - - const int64_t start = MonotonicNow(); - - pid_t pid = vfork(); - if (pid == 0) { - SleepSafe(kChildDelay); - _exit(kChildExitCode); - } - TEST_PCHECK_MSG(pid > 0, "vfork failed"); - MaybeSave(); - - const int64_t end = MonotonicNow(); - - absl::Duration dur = absl::Nanoseconds(end - start); - - TEST_CHECK(dur >= kChildDelay); - - int status = 0; - TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0)); - TEST_CHECK(WIFEXITED(status)); - TEST_CHECK(WEXITSTATUS(status) == kChildExitCode); - }; - - EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0)); -} - -TEST(VforkTest, ParentStopsUntilChildExecves_NoRandomSave) { - ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"}; - char* const* const child_argv = owned_child_argv.get(); - - const auto test = [&] { - const int64_t start = MonotonicNow(); - - pid_t pid = vfork(); - if (pid == 0) { - SleepSafe(kChildDelay); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - _exit(errno); - } - // Don't attempt save/restore until after recording end_time, - // since the test expects an upper bound on the time spent - // stopped. - int saved_errno = errno; - const int64_t end = MonotonicNow(); - errno = saved_errno; - TEST_PCHECK_MSG(pid > 0, "vfork failed"); - MaybeSave(); - - absl::Duration dur = absl::Nanoseconds(end - start); - - // The parent should resume execution after execve, but before - // the post-execve test child exits. - TEST_CHECK(dur >= kChildDelay); - TEST_CHECK(dur <= 2 * kChildDelay); - - int status = 0; - TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0)); - TEST_CHECK(WIFEXITED(status)); - TEST_CHECK(WEXITSTATUS(status) == kChildExitCode); - }; - - EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0)); -} - -// A vfork child does not unstop the parent a second time when it exits after -// exec. -TEST(VforkTest, ExecedChildExitDoesntUnstopParent_NoRandomSave) { - ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"}; - char* const* const child_argv = owned_child_argv.get(); - - const auto test = [&] { - pid_t pid1 = vfork(); - if (pid1 == 0) { - execve(child_argv[0], child_argv, /* envp = */ nullptr); - _exit(errno); - } - TEST_PCHECK_MSG(pid1 > 0, "vfork failed"); - MaybeSave(); - - // pid1 exec'd and is now sleeping. - SleepSafe(kChildDelay / 2); - - const int64_t start = MonotonicNow(); - - pid_t pid2 = vfork(); - if (pid2 == 0) { - SleepSafe(kChildDelay); - _exit(kChildExitCode); - } - TEST_PCHECK_MSG(pid2 > 0, "vfork failed"); - MaybeSave(); - - const int64_t end = MonotonicNow(); - - absl::Duration dur = absl::Nanoseconds(end - start); - - // The parent should resume execution only after pid2 exits, not - // when pid1 exits. - TEST_CHECK(dur >= kChildDelay); - - int status = 0; - TEST_PCHECK(RetryEINTR(waitpid)(pid1, &status, 0)); - TEST_CHECK(WIFEXITED(status)); - TEST_CHECK(WEXITSTATUS(status) == kChildExitCode); - - TEST_PCHECK(RetryEINTR(waitpid)(pid2, &status, 0)); - TEST_CHECK(WIFEXITED(status)); - TEST_CHECK(WEXITSTATUS(status) == kChildExitCode); - }; - - EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0)); -} - -int RunChild() { - SleepSafe(kChildDelay); - return kChildExitCode; -} - -} // namespace - -} // namespace testing -} // namespace gvisor - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - if (absl::GetFlag(FLAGS_vfork_test_child)) { - return gvisor::testing::RunChild(); - } - - return gvisor::testing::RunAllTests(); -} diff --git a/test/syscalls/linux/vsyscall.cc b/test/syscalls/linux/vsyscall.cc deleted file mode 100644 index ae4377108..000000000 --- a/test/syscalls/linux/vsyscall.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <time.h> - -#include "gtest/gtest.h" -#include "test/util/proc_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -#if defined(__x86_64__) || defined(__i386__) -time_t vsyscall_time(time_t* t) { - constexpr uint64_t kVsyscallTimeEntry = 0xffffffffff600400; - return reinterpret_cast<time_t (*)(time_t*)>(kVsyscallTimeEntry)(t); -} - -TEST(VsyscallTest, VsyscallAlwaysAvailableOnGvisor) { - SKIP_IF(!IsRunningOnGvisor()); - // Vsyscall is always advertised by gvisor. - EXPECT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(IsVsyscallEnabled())); - // Vsyscall should always works on gvisor. - time_t t; - EXPECT_THAT(vsyscall_time(&t), SyscallSucceeds()); -} -#endif - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc deleted file mode 100644 index 944149d5e..000000000 --- a/test/syscalls/linux/wait.cc +++ /dev/null @@ -1,913 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> -#include <sys/mman.h> -#include <sys/ptrace.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -#include <functional> -#include <tuple> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/logging.h" -#include "test/util/multiprocess_util.h" -#include "test/util/posix_error.h" -#include "test/util/signal_util.h" -#include "test/util/test_util.h" -#include "test/util/thread_util.h" -#include "test/util/time_util.h" - -using ::testing::UnorderedElementsAre; - -// These unit tests focus on the wait4(2) system call, but include a basic -// checks for the i386 waitpid(2) syscall, which is a subset of wait4(2). -// -// NOTE(b/22640830,b/27680907,b/29049891): Some functionality is not tested as -// it is not currently supported by gVisor: -// * Process groups. -// * Core dump status (WCOREDUMP). -// -// Tests for waiting on stopped/continued children are in sigstop.cc. - -namespace gvisor { -namespace testing { - -namespace { - -// The CloneChild function seems to need more than one page of stack space. -static const size_t kStackSize = 2 * kPageSize; - -// The child thread created in CloneAndExit runs this function. -// This child does not have the TLS setup, so it must not use glibc functions. -int CloneChild(void* priv) { - int64_t sleep = reinterpret_cast<int64_t>(priv); - SleepSafe(absl::Seconds(sleep)); - - // glibc's _exit(2) function wrapper will helpfully call exit_group(2), - // exiting the entire process. - syscall(__NR_exit, 0); - return 1; -} - -// ForkAndExit forks a child process which exits with exit_code, after -// sleeping for the specified duration (seconds). -pid_t ForkAndExit(int exit_code, int64_t sleep) { - pid_t child = fork(); - if (child == 0) { - SleepSafe(absl::Seconds(sleep)); - _exit(exit_code); - } - return child; -} - -int64_t clock_gettime_nsecs(clockid_t id) { - struct timespec ts; - TEST_PCHECK(clock_gettime(id, &ts) == 0); - return (ts.tv_sec * 1000000000 + ts.tv_nsec); -} - -void spin(int64_t sec) { - int64_t ns = sec * 1000000000; - int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID); - int64_t end = start + ns; - - do { - constexpr int kLoopCount = 1000000; // large and arbitrary - // volatile to prevent the compiler from skipping this loop. - for (volatile int i = 0; i < kLoopCount; i++) { - } - } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end); -} - -// ForkSpinAndExit forks a child process which exits with exit_code, after -// spinning for the specified duration (seconds). -pid_t ForkSpinAndExit(int exit_code, int64_t spintime) { - pid_t child = fork(); - if (child == 0) { - spin(spintime); - _exit(exit_code); - } - return child; -} - -absl::Duration RusageCpuTime(const struct rusage& ru) { - return absl::DurationFromTimeval(ru.ru_utime) + - absl::DurationFromTimeval(ru.ru_stime); -} - -// Returns the address of the top of the stack. -// Free with FreeStack. -uintptr_t AllocStack() { - void* addr = mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - if (addr == MAP_FAILED) { - return reinterpret_cast<uintptr_t>(MAP_FAILED); - } - - return reinterpret_cast<uintptr_t>(addr) + kStackSize; -} - -// Frees a stack page allocated with AllocStack. -int FreeStack(uintptr_t addr) { - addr -= kStackSize; - return munmap(reinterpret_cast<void*>(addr), kPageSize); -} - -// CloneAndExit clones a child thread, which exits with 0 after sleeping for -// the specified duration (must be in seconds). extra_flags are ORed against -// the standard clone(2) flags. -int CloneAndExit(int64_t sleep, uintptr_t stack, int extra_flags) { - return clone(CloneChild, reinterpret_cast<void*>(stack), - CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_VM | extra_flags, - reinterpret_cast<void*>(sleep)); -} - -// Simple wrappers around wait4(2) and waitid(2) that ignore interrupts. -constexpr auto Wait4 = RetryEINTR(wait4); -constexpr auto Waitid = RetryEINTR(waitid); - -// Fixture for tests parameterized by a function that waits for any child to -// exit with the given options, checks that it exited with the given code, and -// then returns its PID. -// -// N.B. These tests run in a multi-threaded environment. We assume that -// background threads do not create child processes and are not themselves -// created with clone(... | SIGCHLD). Either may cause these tests to -// erroneously wait on child processes/threads. -class WaitAnyChildTest : public ::testing::TestWithParam< - std::function<PosixErrorOr<pid_t>(int, int)>> { - protected: - PosixErrorOr<pid_t> WaitAny(int code) { return WaitAnyWithOptions(code, 0); } - - PosixErrorOr<pid_t> WaitAnyWithOptions(int code, int options) { - return GetParam()(code, options); - } -}; - -// Wait for any child to exit. -TEST_P(WaitAnyChildTest, Fork) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child)); -} - -// Call wait4 for any process after the child has already exited. -TEST_P(WaitAnyChildTest, AfterExit) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - absl::SleepFor(absl::Seconds(5)); - - EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child)); -} - -// Wait for multiple children to exit, waiting for either at a time. -TEST_P(WaitAnyChildTest, MultipleFork) { - pid_t child1, child2; - ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds()); - ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds()); - - std::vector<pid_t> pids; - pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0))); - pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0))); - EXPECT_THAT(pids, UnorderedElementsAre(child1, child2)); -} - -// Wait for any child to exit. -// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like -// a forked process. -TEST_P(WaitAnyChildTest, CloneSIGCHLD) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds()); - - EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child)); -} - -// Wait for a child thread and process. -TEST_P(WaitAnyChildTest, ForkAndClone) { - pid_t process; - ASSERT_THAT(process = ForkAndExit(0, 0), SyscallSucceeds()); - - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int thread; - // Send SIGCHLD for normal wait semantics. - ASSERT_THAT(thread = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds()); - - std::vector<pid_t> pids; - pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0))); - pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0))); - EXPECT_THAT(pids, UnorderedElementsAre(process, thread)); -} - -// Return immediately if no child has exited. -TEST_P(WaitAnyChildTest, WaitWNOHANG) { - EXPECT_THAT(WaitAnyWithOptions(0, WNOHANG), - PosixErrorIs(ECHILD, ::testing::_)); -} - -// Bad options passed -TEST_P(WaitAnyChildTest, BadOption) { - EXPECT_THAT(WaitAnyWithOptions(0, 123456), - PosixErrorIs(EINVAL, ::testing::_)); -} - -TEST_P(WaitAnyChildTest, WaitedChildRusage) { - struct rusage before; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds()); - - pid_t child; - constexpr absl::Duration kSpin = absl::Seconds(3); - ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)), - SyscallSucceeds()); - ASSERT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child)); - - struct rusage after; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds()); - - EXPECT_GE(RusageCpuTime(after) - RusageCpuTime(before), kSpin); -} - -TEST_P(WaitAnyChildTest, IgnoredChildRusage) { - // "POSIX.1-2001 specifies that if the disposition of SIGCHLD is - // set to SIG_IGN or the SA_NOCLDWAIT flag is set for SIGCHLD (see - // sigaction(2)), then children that terminate do not become zombies and a - // call to wait() or waitpid() will block until all children have terminated, - // and then fail with errno set to ECHILD." - waitpid(2) - // - // "RUSAGE_CHILDREN: Return resource usage statistics for all children of the - // calling process that have terminated *and been waited for*." - - // getrusage(2), emphasis added - - struct sigaction sa; - sa.sa_handler = SIG_IGN; - const auto cleanup_sigact = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa)); - - struct rusage before; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds()); - - const absl::Duration start = - absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC)); - - constexpr absl::Duration kSpin = absl::Seconds(3); - - // ForkAndSpin uses CLOCK_THREAD_CPUTIME_ID, which is lower resolution than, - // and may diverge from, CLOCK_MONOTONIC, so we allow a small grace period but - // still check that we blocked for a while. - constexpr absl::Duration kSpinGrace = absl::Milliseconds(100); - - pid_t child; - ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)), - SyscallSucceeds()); - ASSERT_THAT(WaitAny(0), PosixErrorIs(ECHILD, ::testing::_)); - const absl::Duration end = - absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC)); - EXPECT_GE(end - start, kSpin - kSpinGrace); - - struct rusage after; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds()); - EXPECT_EQ(before.ru_utime.tv_sec, after.ru_utime.tv_sec); - EXPECT_EQ(before.ru_utime.tv_usec, after.ru_utime.tv_usec); - EXPECT_EQ(before.ru_stime.tv_sec, after.ru_stime.tv_sec); - EXPECT_EQ(before.ru_stime.tv_usec, after.ru_stime.tv_usec); -} - -INSTANTIATE_TEST_SUITE_P( - Waiters, WaitAnyChildTest, - ::testing::Values( - [](int code, int options) -> PosixErrorOr<pid_t> { - int status; - auto const pid = Wait4(-1, &status, options, nullptr); - MaybeSave(); - if (pid < 0) { - return PosixError(errno, "wait4"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != code) { - return PosixError( - EINVAL, absl::StrCat("unexpected wait status: got ", status, - ", wanted ", code)); - } - return static_cast<pid_t>(pid); - }, - [](int code, int options) -> PosixErrorOr<pid_t> { - siginfo_t si; - auto const rv = Waitid(P_ALL, 0, &si, WEXITED | options); - MaybeSave(); - if (rv < 0) { - return PosixError(errno, "waitid"); - } - if (si.si_signo != SIGCHLD) { - return PosixError( - EINVAL, absl::StrCat("unexpected signo: got ", si.si_signo, - ", wanted ", SIGCHLD)); - } - if (si.si_status != code) { - return PosixError( - EINVAL, absl::StrCat("unexpected status: got ", si.si_status, - ", wanted ", code)); - } - if (si.si_code != CLD_EXITED) { - return PosixError(EINVAL, - absl::StrCat("unexpected code: got ", si.si_code, - ", wanted ", CLD_EXITED)); - } - auto const uid = getuid(); - if (si.si_uid != uid) { - return PosixError(EINVAL, - absl::StrCat("unexpected uid: got ", si.si_uid, - ", wanted ", uid)); - } - return static_cast<pid_t>(si.si_pid); - })); - -// Fixture for tests parameterized by a (sysno, function) tuple. The function -// takes the PID of a specific child to wait for, waits for it to exit, and -// checks that it exits with the given code. -class WaitSpecificChildTest - : public ::testing::TestWithParam< - std::tuple<int, std::function<PosixError(pid_t, int, int)>>> { - protected: - int Sysno() { return std::get<0>(GetParam()); } - - PosixError WaitForWithOptions(pid_t pid, int options, int code) { - return std::get<1>(GetParam())(pid, options, code); - } - - PosixError WaitFor(pid_t pid, int code) { - return std::get<1>(GetParam())(pid, 0, code); - } -}; - -// Wait for specific child to exit. -TEST_P(WaitSpecificChildTest, Fork) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// Non-zero exit codes are correctly propagated. -TEST_P(WaitSpecificChildTest, NormalExit) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child, 42)); -} - -// Wait for multiple children to exit. -TEST_P(WaitSpecificChildTest, MultipleFork) { - pid_t child1, child2; - ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds()); - ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child1, 0)); - EXPECT_NO_ERRNO(WaitFor(child2, 0)); -} - -// Wait for multiple children to exit, out of the order they were created. -TEST_P(WaitSpecificChildTest, MultipleForkOutOfOrder) { - pid_t child1, child2; - ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds()); - ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child2, 0)); - EXPECT_NO_ERRNO(WaitFor(child1, 0)); -} - -// Wait for specific child to exit, entering wait4 before the exit occurs. -TEST_P(WaitSpecificChildTest, ForkSleep) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// Wait should block until the child exits. -TEST_P(WaitSpecificChildTest, ForkBlock) { - pid_t child; - - auto start = absl::Now(); - ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); - - EXPECT_GE(absl::Now() - start, absl::Seconds(5)); -} - -// Waiting after the child has already exited returns immediately. -TEST_P(WaitSpecificChildTest, AfterExit) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - absl::SleepFor(absl::Seconds(5)); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// Wait for child of sibling thread. -TEST_P(WaitSpecificChildTest, SiblingChildren) { - absl::Mutex mu; - pid_t child; - bool ready = false; - bool stop = false; - - ScopedThread t([&] { - absl::MutexLock ml(&mu); - EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - ready = true; - mu.Await(absl::Condition(&stop)); - }); - - // N.B. This must be declared after ScopedThread, so it is destructed first, - // thus waking the thread. - absl::MutexLock ml(&mu); - mu.Await(absl::Condition(&ready)); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); - - // Keep the sibling alive until after we've waited so the child isn't - // reparented. - stop = true; -} - -// Waiting for child of sibling thread not allowed with WNOTHREAD. -TEST_P(WaitSpecificChildTest, SiblingChildrenWNOTHREAD) { - // Linux added WNOTHREAD support to waitid(2) in - // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to - // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. - // - // Skip the test if it isn't supported yet. - if (Sysno() == SYS_waitid) { - int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WNOTHREAD); - SKIP_IF(ret < 0 && errno == EINVAL); - } - - absl::Mutex mu; - pid_t child; - bool ready = false; - bool stop = false; - - ScopedThread t([&] { - absl::MutexLock ml(&mu); - EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - ready = true; - mu.Await(absl::Condition(&stop)); - - // This thread can wait on child. - EXPECT_NO_ERRNO(WaitForWithOptions(child, __WNOTHREAD, 0)); - }); - - // N.B. This must be declared after ScopedThread, so it is destructed first, - // thus waking the thread. - absl::MutexLock ml(&mu); - mu.Await(absl::Condition(&ready)); - - // This thread can't wait on child. - EXPECT_THAT(WaitForWithOptions(child, __WNOTHREAD, 0), - PosixErrorIs(ECHILD, ::testing::_)); - - // Keep the sibling alive until after we've waited so the child isn't - // reparented. - stop = true; -} - -// Wait for specific child to exit. -// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like -// a forked process. -TEST_P(WaitSpecificChildTest, CloneSIGCHLD) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// Wait for specific child to exit. -// A non-CLONE_THREAD child which does not send SIGCHLD upon exit can be waited -// on, but returns ECHILD. -TEST_P(WaitSpecificChildTest, CloneNoSIGCHLD) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); - - EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_)); -} - -// Waiting after the child has already exited returns immediately. -TEST_P(WaitSpecificChildTest, CloneAfterExit) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - // Send SIGCHLD for normal wait semantics. - ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds()); - - absl::SleepFor(absl::Seconds(5)); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// A CLONE_THREAD child cannot be waited on. -TEST_P(WaitSpecificChildTest, CloneThread) { - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(15, stack, CLONE_THREAD), SyscallSucceeds()); - auto start = absl::Now(); - - EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_)); - - // Ensure wait4 didn't block. - EXPECT_LE(absl::Now() - start, absl::Seconds(10)); - - // Since we can't wait on the child, we sleep to try to avoid freeing its - // stack before it exits. - absl::SleepFor(absl::Seconds(5)); -} - -// A child that does not send a SIGCHLD on exit may be waited on with -// the __WCLONE flag. -TEST_P(WaitSpecificChildTest, CloneWCLONE) { - // Linux added WCLONE support to waitid(2) in - // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to - // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. - // - // Skip the test if it isn't supported yet. - if (Sysno() == SYS_waitid) { - int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE); - SKIP_IF(ret < 0 && errno == EINVAL); - } - - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - int child; - ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitForWithOptions(child, __WCLONE, 0)); -} - -// A forked child cannot be waited on with WCLONE. -TEST_P(WaitSpecificChildTest, ForkWCLONE) { - // Linux added WCLONE support to waitid(2) in - // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to - // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. - // - // Skip the test if it isn't supported yet. - if (Sysno() == SYS_waitid) { - int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE); - SKIP_IF(ret < 0 && errno == EINVAL); - } - - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_THAT(WaitForWithOptions(child, WNOHANG | __WCLONE, 0), - PosixErrorIs(ECHILD, ::testing::_)); - - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -// Any type of child can be waited on with WALL. -TEST_P(WaitSpecificChildTest, WALL) { - // Linux added WALL support to waitid(2) in - // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to - // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7. - // - // Skip the test if it isn't supported yet. - if (Sysno() == SYS_waitid) { - int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WALL); - SKIP_IF(ret < 0 && errno == EINVAL); - } - - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0)); - - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds()); - - EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0)); -} - -// Return ECHILD for bad child. -TEST_P(WaitSpecificChildTest, BadChild) { - EXPECT_THAT(WaitFor(42, 0), PosixErrorIs(ECHILD, ::testing::_)); -} - -// Wait for a child process that only exits after calling execve(2) from a -// non-leader thread. -TEST_P(WaitSpecificChildTest, AfterChildExecve) { - ExecveArray const owned_child_argv = {"/bin/true"}; - char* const* const child_argv = owned_child_argv.get(); - - uintptr_t stack; - ASSERT_THAT(stack = AllocStack(), SyscallSucceeds()); - auto free = - Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); }); - - pid_t const child = fork(); - if (child == 0) { - // Give the parent some time to start waiting. - SleepSafe(absl::Seconds(5)); - // Pass CLONE_VFORK to block the original thread in the child process until - // the clone thread calls execve, annihilating them both. (This means that - // if clone returns at all, something went wrong.) - // - // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's - // x86_64 implementation is safe. See glibc - // sysdeps/unix/sysv/linux/x86_64/clone.S. - clone( - +[](void* arg) { - auto child_argv = static_cast<char* const*>(arg); - execve(child_argv[0], child_argv, /* envp = */ nullptr); - return errno; - }, - reinterpret_cast<void*>(stack), - CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM | - CLONE_VFORK, - const_cast<char**>(child_argv)); - _exit(errno); - } - ASSERT_THAT(child, SyscallSucceeds()); - EXPECT_NO_ERRNO(WaitFor(child, 0)); -} - -PosixError CheckWait4(pid_t pid, int options, int code) { - int status; - auto const rv = Wait4(pid, &status, options, nullptr); - MaybeSave(); - if (rv < 0) { - return PosixError(errno, "wait4"); - } else if (rv != pid) { - return PosixError( - EINVAL, absl::StrCat("unexpected pid: got ", rv, ", wanted ", pid)); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != code) { - return PosixError(EINVAL, absl::StrCat("unexpected wait status: got ", - status, ", wanted ", code)); - } - return NoError(); -}; - -PosixError CheckWaitid(pid_t pid, int options, int code) { - siginfo_t si; - auto const rv = Waitid(P_PID, pid, &si, options | WEXITED); - MaybeSave(); - if (rv < 0) { - return PosixError(errno, "waitid"); - } - if (si.si_pid != pid) { - return PosixError(EINVAL, absl::StrCat("unexpected pid: got ", si.si_pid, - ", wanted ", pid)); - } - if (si.si_signo != SIGCHLD) { - return PosixError(EINVAL, absl::StrCat("unexpected signo: got ", - si.si_signo, ", wanted ", SIGCHLD)); - } - if (si.si_status != code) { - return PosixError(EINVAL, absl::StrCat("unexpected status: got ", - si.si_status, ", wanted ", code)); - } - if (si.si_code != CLD_EXITED) { - return PosixError(EINVAL, absl::StrCat("unexpected code: got ", si.si_code, - ", wanted ", CLD_EXITED)); - } - return NoError(); -} - -INSTANTIATE_TEST_SUITE_P( - Waiters, WaitSpecificChildTest, - ::testing::Values(std::make_tuple(SYS_wait4, CheckWait4), - std::make_tuple(SYS_waitid, CheckWaitid))); - -// WIFEXITED, WIFSIGNALED, WTERMSIG indicate signal exit. -TEST(WaitTest, SignalExit) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 10), SyscallSucceeds()); - - EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds()); - - int status; - EXPECT_THAT(Wait4(child, &status, 0, nullptr), - SyscallSucceedsWithValue(child)); - - EXPECT_FALSE(WIFEXITED(status)); - EXPECT_TRUE(WIFSIGNALED(status)); - EXPECT_EQ(SIGKILL, WTERMSIG(status)); -} - -// waitid requires at least one option. -TEST(WaitTest, WaitidOptions) { - EXPECT_THAT(Waitid(P_ALL, 0, nullptr, 0), SyscallFailsWithErrno(EINVAL)); -} - -// waitid does not wait for a child to exit if not passed WEXITED. -TEST(WaitTest, WaitidNoWEXITED) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds()); - EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WSTOPPED), - SyscallFailsWithErrno(ECHILD)); - EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WEXITED), SyscallSucceeds()); -} - -// WNOWAIT allows the same wait result to be returned again. -TEST(WaitTest, WaitidWNOWAIT) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds()); - - siginfo_t info; - ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED | WNOWAIT), - SyscallSucceeds()); - EXPECT_EQ(child, info.si_pid); - EXPECT_EQ(SIGCHLD, info.si_signo); - EXPECT_EQ(CLD_EXITED, info.si_code); - EXPECT_EQ(42, info.si_status); - - ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED), SyscallSucceeds()); - EXPECT_EQ(child, info.si_pid); - EXPECT_EQ(SIGCHLD, info.si_signo); - EXPECT_EQ(CLD_EXITED, info.si_code); - EXPECT_EQ(42, info.si_status); - - EXPECT_THAT(Waitid(P_PID, child, &info, WEXITED), - SyscallFailsWithErrno(ECHILD)); -} - -// waitpid(pid, status, options) is equivalent to -// wait4(pid, status, options, nullptr). -// This is a dedicated syscall on i386, glibc maps it to wait4 on amd64. -TEST(WaitTest, WaitPid) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds()); - - int status; - EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), - SyscallSucceedsWithValue(child)); - - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(42, WEXITSTATUS(status)); -} - -// Test that signaling a zombie succeeds. This is a signals test that is in this -// file for some reason. -TEST(WaitTest, KillZombie) { - pid_t child; - ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds()); - - // Sleep for three seconds to ensure the child has exited. - absl::SleepFor(absl::Seconds(3)); - - // The child is now a zombie. Check that killing it returns 0. - EXPECT_THAT(kill(child, SIGTERM), SyscallSucceeds()); - EXPECT_THAT(kill(child, 0), SyscallSucceeds()); - - EXPECT_THAT(Wait4(child, nullptr, 0, nullptr), - SyscallSucceedsWithValue(child)); -} - -TEST(WaitTest, Wait4Rusage) { - pid_t child; - constexpr absl::Duration kSpin = absl::Seconds(3); - ASSERT_THAT(child = ForkSpinAndExit(21, absl::ToInt64Seconds(kSpin)), - SyscallSucceeds()); - - int status; - struct rusage rusage = {}; - ASSERT_THAT(Wait4(child, &status, 0, &rusage), - SyscallSucceedsWithValue(child)); - - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(21, WEXITSTATUS(status)); - - EXPECT_GE(RusageCpuTime(rusage), kSpin); -} - -TEST(WaitTest, WaitidRusage) { - pid_t child; - constexpr absl::Duration kSpin = absl::Seconds(3); - ASSERT_THAT(child = ForkSpinAndExit(27, absl::ToInt64Seconds(kSpin)), - SyscallSucceeds()); - - siginfo_t si = {}; - struct rusage rusage = {}; - - // From waitid(2): - // The raw waitid() system call takes a fifth argument, of type - // struct rusage *. If this argument is non-NULL, then it is used - // to return resource usage information about the child, in the - // same manner as wait4(2). - EXPECT_THAT( - RetryEINTR(syscall)(SYS_waitid, P_PID, child, &si, WEXITED, &rusage), - SyscallSucceeds()); - EXPECT_EQ(si.si_signo, SIGCHLD); - EXPECT_EQ(si.si_code, CLD_EXITED); - EXPECT_EQ(si.si_status, 27); - EXPECT_EQ(si.si_pid, child); - - EXPECT_GE(RusageCpuTime(rusage), kSpin); -} - -// After bf959931ddb88c4e4366e96dd22e68fa0db9527c ("wait/ptrace: assume __WALL -// if the child is traced") (Linux 4.7), tracees are always eligible for -// waiting, regardless of type. -TEST(WaitTest, TraceeWALL) { - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - pid_t child = fork(); - if (child == 0) { - // Child. - rfd.reset(); - - TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == 0); - - // Notify parent that we're now a tracee. - wfd.reset(); - - _exit(0); - } - ASSERT_THAT(child, SyscallSucceeds()); - - wfd.reset(); - - // Wait for child to become tracee. - char c; - EXPECT_THAT(ReadFd(rfd.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0)); - - // We can wait on the fork child with WCLONE, as it is a tracee. - int status; - if (IsRunningOnGvisor()) { - ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr), - SyscallSucceedsWithValue(child)); - - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; - } else { - // On older versions of Linux, we may get ECHILD. - ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr), - ::testing::AnyOf(SyscallSucceedsWithValue(child), - SyscallFailsWithErrno(ECHILD))); - } -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/write.cc b/test/syscalls/linux/write.cc deleted file mode 100644 index 3373ba72b..000000000 --- a/test/syscalls/linux/write.cc +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <signal.h> -#include <sys/mman.h> -#include <sys/resource.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/cleanup.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// TODO(gvisor.dev/issue/2370): This test is currently very rudimentary. -class WriteTest : public ::testing::Test { - public: - ssize_t WriteBytes(int fd, int bytes) { - std::vector<char> buf(bytes); - std::fill(buf.begin(), buf.end(), 'a'); - return WriteFd(fd, buf.data(), buf.size()); - } -}; - -TEST_F(WriteTest, WriteNoExceedsRLimit) { - // Get the current rlimit and restore after test run. - struct rlimit initial_lim; - ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - auto cleanup = Cleanup([&initial_lim] { - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - }); - - int fd; - struct rlimit setlim; - const int target_lim = 1024; - setlim.rlim_cur = target_lim; - setlim.rlim_max = RLIM_INFINITY; - const std::string pathname = NewTempAbsPath(); - ASSERT_THAT(fd = open(pathname.c_str(), O_WRONLY | O_CREAT, S_IRWXU), - SyscallSucceeds()); - ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds()); - - EXPECT_THAT(WriteBytes(fd, target_lim), SyscallSucceedsWithValue(target_lim)); - - std::vector<char> buf(target_lim + 1); - std::fill(buf.begin(), buf.end(), 'a'); - EXPECT_THAT(pwrite(fd, buf.data(), target_lim, 1), SyscallSucceeds()); - EXPECT_THAT(pwrite64(fd, buf.data(), target_lim, 1), SyscallSucceeds()); - - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(WriteTest, WriteExceedsRLimit) { - // Get the current rlimit and restore after test run. - struct rlimit initial_lim; - ASSERT_THAT(getrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - auto cleanup = Cleanup([&initial_lim] { - EXPECT_THAT(setrlimit(RLIMIT_FSIZE, &initial_lim), SyscallSucceeds()); - }); - - int fd; - sigset_t filesize_mask; - sigemptyset(&filesize_mask); - sigaddset(&filesize_mask, SIGXFSZ); - - struct rlimit setlim; - const int target_lim = 1024; - setlim.rlim_cur = target_lim; - setlim.rlim_max = RLIM_INFINITY; - - const std::string pathname = NewTempAbsPath(); - ASSERT_THAT(fd = open(pathname.c_str(), O_WRONLY | O_CREAT, S_IRWXU), - SyscallSucceeds()); - ASSERT_THAT(setrlimit(RLIMIT_FSIZE, &setlim), SyscallSucceeds()); - ASSERT_THAT(sigprocmask(SIG_BLOCK, &filesize_mask, nullptr), - SyscallSucceeds()); - std::vector<char> buf(target_lim + 2); - std::fill(buf.begin(), buf.end(), 'a'); - - EXPECT_THAT(write(fd, buf.data(), target_lim + 1), - SyscallSucceedsWithValue(target_lim)); - EXPECT_THAT(write(fd, buf.data(), 1), SyscallFailsWithErrno(EFBIG)); - siginfo_t info; - struct timespec timelimit = {0, 0}; - ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit), - SyscallSucceedsWithValue(SIGXFSZ)); - EXPECT_EQ(info.si_code, SI_USER); - EXPECT_EQ(info.si_pid, getpid()); - EXPECT_EQ(info.si_uid, getuid()); - - EXPECT_THAT(pwrite(fd, buf.data(), target_lim + 1, 1), - SyscallSucceedsWithValue(target_lim - 1)); - EXPECT_THAT(pwrite(fd, buf.data(), 1, target_lim), - SyscallFailsWithErrno(EFBIG)); - ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit), - SyscallSucceedsWithValue(SIGXFSZ)); - EXPECT_EQ(info.si_code, SI_USER); - EXPECT_EQ(info.si_pid, getpid()); - EXPECT_EQ(info.si_uid, getuid()); - - EXPECT_THAT(pwrite64(fd, buf.data(), target_lim + 1, 1), - SyscallSucceedsWithValue(target_lim - 1)); - EXPECT_THAT(pwrite64(fd, buf.data(), 1, target_lim), - SyscallFailsWithErrno(EFBIG)); - ASSERT_THAT(RetryEINTR(sigtimedwait)(&filesize_mask, &info, &timelimit), - SyscallSucceedsWithValue(SIGXFSZ)); - EXPECT_EQ(info.si_code, SI_USER); - EXPECT_EQ(info.si_pid, getpid()); - EXPECT_EQ(info.si_uid, getuid()); - - ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &filesize_mask, nullptr), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); -} - -TEST_F(WriteTest, WriteIncrementOffset) { - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_WRONLY)); - int fd = f.get(); - - EXPECT_THAT(WriteBytes(fd, 0), SyscallSucceedsWithValue(0)); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - const int bytes_total = 1024; - - EXPECT_THAT(WriteBytes(fd, bytes_total), - SyscallSucceedsWithValue(bytes_total)); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(bytes_total)); -} - -TEST_F(WriteTest, WriteIncrementOffsetSeek) { - const std::string data = "hello world\n"; - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), data, TempPath::kDefaultFileMode)); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_WRONLY)); - int fd = f.get(); - - const int seek_offset = data.size() / 2; - ASSERT_THAT(lseek(fd, seek_offset, SEEK_SET), - SyscallSucceedsWithValue(seek_offset)); - - const int write_bytes = 512; - EXPECT_THAT(WriteBytes(fd, write_bytes), - SyscallSucceedsWithValue(write_bytes)); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), - SyscallSucceedsWithValue(seek_offset + write_bytes)); -} - -TEST_F(WriteTest, WriteIncrementOffsetAppend) { - const std::string data = "hello world\n"; - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), data, TempPath::kDefaultFileMode)); - FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE( - Open(tmpfile.path().c_str(), O_WRONLY | O_APPEND)); - int fd = f.get(); - - EXPECT_THAT(WriteBytes(fd, 1024), SyscallSucceedsWithValue(1024)); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), - SyscallSucceedsWithValue(data.size() + 1024)); -} - -TEST_F(WriteTest, WriteIncrementOffsetEOF) { - const std::string data = "hello world\n"; - const TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), data, TempPath::kDefaultFileMode)); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_WRONLY)); - int fd = f.get(); - - EXPECT_THAT(lseek(fd, 0, SEEK_END), SyscallSucceedsWithValue(data.size())); - - EXPECT_THAT(WriteBytes(fd, 1024), SyscallSucceedsWithValue(1024)); - EXPECT_THAT(lseek(fd, 0, SEEK_END), - SyscallSucceedsWithValue(data.size() + 1024)); -} - -TEST_F(WriteTest, PwriteNoChangeOffset) { - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_WRONLY)); - int fd = f.get(); - - const std::string data = "hello world\n"; - - EXPECT_THAT(pwrite(fd, data.data(), data.size(), 0), - SyscallSucceedsWithValue(data.size())); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(0)); - - const int bytes_total = 1024; - ASSERT_THAT(WriteBytes(fd, bytes_total), - SyscallSucceedsWithValue(bytes_total)); - ASSERT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(bytes_total)); - - EXPECT_THAT(pwrite(fd, data.data(), data.size(), bytes_total), - SyscallSucceedsWithValue(data.size())); - EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(bytes_total)); -} - -TEST_F(WriteTest, WriteWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); - int fd = f.get(); - - EXPECT_THAT(WriteBytes(fd, 1024), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(WriteTest, WritevWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); - int fd = f.get(); - - char buf[16]; - struct iovec iov; - iov.iov_base = buf; - iov.iov_len = sizeof(buf); - - EXPECT_THAT(writev(fd, &iov, /*__count=*/1), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(WriteTest, PwriteWithOpath) { - SKIP_IF(IsRunningWithVFS1()); - TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor f = - ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); - int fd = f.get(); - - const std::string data = "hello world\n"; - - EXPECT_THAT(pwrite(fd, data.data(), data.size(), 0), - SyscallFailsWithErrno(EBADF)); -} - -// Test that partial writes that hit SIGSEGV are correctly handled and return -// partial write. -TEST_F(WriteTest, PartialWriteSIGSEGV) { - // Allocate 2 pages and remove permission from the second. - const size_t size = 2 * kPageSize; - void* addr = mmap(0, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - ASSERT_NE(addr, MAP_FAILED); - auto cleanup = Cleanup( - [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); - - void* badAddr = reinterpret_cast<char*>(addr) + kPageSize; - ASSERT_THAT(mprotect(badAddr, kPageSize, PROT_NONE), SyscallSucceeds()); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_WRONLY)); - - // Attempt to write both pages to the file. Create a non-contiguous iovec pair - // to ensure operation is done in 2 steps. - struct iovec iov[] = { - { - .iov_base = addr, - .iov_len = kPageSize, - }, - { - .iov_base = addr, - .iov_len = size, - }, - }; - // Write should succeed for the first iovec and half of the second (=2 pages). - EXPECT_THAT(pwritev(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), - SyscallSucceedsWithValue(2 * kPageSize)); -} - -// Test that partial writes that hit SIGBUS are correctly handled and return -// partial write. -TEST_F(WriteTest, PartialWriteSIGBUS) { - SKIP_IF(getenv("GVISOR_GOFER_UNCACHED")); // Can't mmap from uncached files. - - TempPath mapfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd_map = - ASSERT_NO_ERRNO_AND_VALUE(Open(mapfile.path().c_str(), O_RDWR)); - - // Let the first page be read to force a partial write. - ASSERT_THAT(ftruncate(fd_map.get(), kPageSize), SyscallSucceeds()); - - // Map 2 pages, one of which is not allocated in the backing file. Reading - // from it will trigger a SIGBUS. - const size_t size = 2 * kPageSize; - void* addr = - mmap(NULL, size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd_map.get(), 0); - ASSERT_NE(addr, MAP_FAILED); - auto cleanup = Cleanup( - [addr, size] { EXPECT_THAT(munmap(addr, size), SyscallSucceeds()); }); - - TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_WRONLY)); - - // Attempt to write both pages to the file. Create a non-contiguous iovec pair - // to ensure operation is done in 2 steps. - struct iovec iov[] = { - { - .iov_base = addr, - .iov_len = kPageSize, - }, - { - .iov_base = addr, - .iov_len = size, - }, - }; - // Write should succeed for the first iovec and half of the second (=2 pages). - ASSERT_THAT(pwritev(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0), - SyscallSucceedsWithValue(2 * kPageSize)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc deleted file mode 100644 index a953a55fe..000000000 --- a/test/syscalls/linux/xattr.cc +++ /dev/null @@ -1,711 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <sys/types.h> -#include <sys/xattr.h> -#include <unistd.h> - -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/container/flat_hash_set.h" -#include "test/syscalls/linux/file_base.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -using ::gvisor::testing::IsTmpfs; - -class XattrTest : public FileTest {}; - -TEST_F(XattrTest, XattrNonexistentFile) { - const char* path = "/does/not/exist"; - const char* name = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(ENOENT)); - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENOENT)); - EXPECT_THAT(listxattr(path, nullptr, 0), SyscallFailsWithErrno(ENOENT)); - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(ENOENT)); -} - -TEST_F(XattrTest, XattrNullName) { - const char* path = test_file_name_.c_str(); - - EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(EFAULT)); - EXPECT_THAT(getxattr(path, nullptr, nullptr, 0), - SyscallFailsWithErrno(EFAULT)); - EXPECT_THAT(removexattr(path, nullptr), SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(XattrTest, XattrEmptyName) { - const char* path = test_file_name_.c_str(); - - EXPECT_THAT(setxattr(path, "", nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(ERANGE)); - EXPECT_THAT(getxattr(path, "", nullptr, 0), SyscallFailsWithErrno(ERANGE)); - EXPECT_THAT(removexattr(path, ""), SyscallFailsWithErrno(ERANGE)); -} - -TEST_F(XattrTest, XattrLargeName) { - const char* path = test_file_name_.c_str(); - std::string name = "user."; - name += std::string(XATTR_NAME_MAX - name.length(), 'a'); - - if (!IsRunningOnGvisor()) { - // In gVisor, access to xattrs is controlled with an explicit list of - // allowed names. This name isn't going to be configured to allow access, so - // don't test it. - EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0), - SyscallSucceedsWithValue(0)); - } - - name += "a"; - EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(ERANGE)); - EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0), - SyscallFailsWithErrno(ERANGE)); - EXPECT_THAT(removexattr(path, name.c_str()), SyscallFailsWithErrno(ERANGE)); -} - -TEST_F(XattrTest, XattrInvalidPrefix) { - const char* path = test_file_name_.c_str(); - std::string name(XATTR_NAME_MAX, 'a'); - EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(EOPNOTSUPP)); - EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0), - SyscallFailsWithErrno(EOPNOTSUPP)); - EXPECT_THAT(removexattr(path, name.c_str()), - SyscallFailsWithErrno(EOPNOTSUPP)); -} - -// Do not allow save/restore cycles after making the test file read-only, as -// the restore will fail to open it with r/w permissions. -TEST_F(XattrTest, XattrReadOnly_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - size_t size = sizeof(val); - - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - DisableSave ds; - ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IRUSR)); - - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), - SyscallFailsWithErrno(EACCES)); - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EACCES)); - - char buf = '-'; - EXPECT_THAT(getxattr(path, name, &buf, size), SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, val); - - char list[sizeof(name)]; - EXPECT_THAT(listxattr(path, list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); -} - -// Do not allow save/restore cycles after making the test file write-only, as -// the restore will fail to open it with r/w permissions. -TEST_F(XattrTest, XattrWriteOnly_NoRandomSave) { - // Drop capabilities that allow us to override file and directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - - DisableSave ds; - ASSERT_NO_ERRNO(testing::Chmod(test_file_name_, S_IWUSR)); - - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - size_t size = sizeof(val); - - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(EACCES)); - - // listxattr will succeed even without read permissions. - char list[sizeof(name)]; - EXPECT_THAT(listxattr(path, list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - EXPECT_THAT(removexattr(path, name), SyscallSucceeds()); -} - -TEST_F(XattrTest, XattrTrustedWithNonadmin) { - // TODO(b/148380782): Support setxattr and getxattr with "trusted" prefix. - SKIP_IF(IsRunningOnGvisor()); - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - const char* path = test_file_name_.c_str(); - const char name[] = "trusted.abc"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, XattrOnDirectory) { - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(dir.path().c_str(), name, nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(getxattr(dir.path().c_str(), name, nullptr, 0), - SyscallSucceedsWithValue(0)); - - char list[sizeof(name)]; - EXPECT_THAT(listxattr(dir.path().c_str(), list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - EXPECT_THAT(removexattr(dir.path().c_str(), name), SyscallSucceeds()); -} - -TEST_F(XattrTest, XattrOnSymlink) { - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(dir.path(), test_file_name_)); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(link.path().c_str(), name, nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(getxattr(link.path().c_str(), name, nullptr, 0), - SyscallSucceedsWithValue(0)); - - char list[sizeof(name)]; - EXPECT_THAT(listxattr(link.path().c_str(), list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - EXPECT_THAT(removexattr(link.path().c_str(), name), SyscallSucceeds()); -} - -TEST_F(XattrTest, XattrOnInvalidFileTypes) { - const char name[] = "user.test"; - - char char_device[] = "/dev/zero"; - EXPECT_THAT(setxattr(char_device, name, nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(getxattr(char_device, name, nullptr, 0), - SyscallFailsWithErrno(ENODATA)); - EXPECT_THAT(listxattr(char_device, nullptr, 0), SyscallSucceedsWithValue(0)); - - // Use tmpfs, where creation of named pipes is supported. - const std::string fifo = NewTempAbsPathInDir("/dev/shm"); - const char* path = fifo.c_str(); - EXPECT_THAT(mknod(path, S_IFIFO | S_IRUSR | S_IWUSR, 0), SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); - EXPECT_THAT(listxattr(path, nullptr, 0), SyscallSucceedsWithValue(0)); - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM)); -} - -TEST_F(XattrTest, SetXattrSizeSmallerThanValue) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - std::vector<char> val = {'a', 'a'}; - size_t size = 1; - EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> buf = {'-', '-'}; - std::vector<char> expected_buf = {'a', '-'}; - EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()), - SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, expected_buf); -} - -TEST_F(XattrTest, SetXattrZeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - EXPECT_THAT(setxattr(path, name, &val, 0, /*flags=*/0), SyscallSucceeds()); - - char buf = '-'; - EXPECT_THAT(getxattr(path, name, &buf, XATTR_SIZE_MAX), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(buf, '-'); -} - -TEST_F(XattrTest, SetXattrSizeTooLarge) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - - // Note that each particular fs implementation may stipulate a lower size - // limit, in which case we actually may fail (e.g. error with ENOSPC) for - // some sizes under XATTR_SIZE_MAX. - size_t size = XATTR_SIZE_MAX + 1; - std::vector<char> val(size); - EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0), - SyscallFailsWithErrno(E2BIG)); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, SetXattrNullValueAndNonzeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 1, /*flags=*/0), - SyscallFailsWithErrno(EFAULT)); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, SetXattrNullValueAndZeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(XattrTest, SetXattrValueTooLargeButOKSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - std::vector<char> val(XATTR_SIZE_MAX + 1); - std::fill(val.begin(), val.end(), 'a'); - size_t size = 1; - EXPECT_THAT(setxattr(path, name, val.data(), size, /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> buf = {'-', '-'}; - std::vector<char> expected_buf = {'a', '-'}; - EXPECT_THAT(getxattr(path, name, buf.data(), size), - SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, expected_buf); -} - -TEST_F(XattrTest, SetXattrReplaceWithSmaller) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - std::vector<char> val = {'a', 'a'}; - EXPECT_THAT(setxattr(path, name, val.data(), 2, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name, val.data(), 1, /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> buf = {'-', '-'}; - std::vector<char> expected_buf = {'a', '-'}; - EXPECT_THAT(getxattr(path, name, buf.data(), 2), SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, expected_buf); -} - -TEST_F(XattrTest, SetXattrReplaceWithLarger) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - std::vector<char> val = {'a', 'a'}; - EXPECT_THAT(setxattr(path, name, val.data(), 1, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name, val.data(), 2, /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> buf = {'-', '-'}; - EXPECT_THAT(getxattr(path, name, buf.data(), 2), SyscallSucceedsWithValue(2)); - EXPECT_EQ(buf, val); -} - -TEST_F(XattrTest, SetXattrCreateFlag) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_CREATE), - SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_CREATE), - SyscallFailsWithErrno(EEXIST)); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(XattrTest, SetXattrReplaceFlag) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_REPLACE), - SyscallFailsWithErrno(ENODATA)); - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name, nullptr, 0, XATTR_REPLACE), - SyscallSucceeds()); - - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(0)); -} - -TEST_F(XattrTest, SetXattrInvalidFlags) { - const char* path = test_file_name_.c_str(); - int invalid_flags = 0xff; - EXPECT_THAT(setxattr(path, nullptr, nullptr, 0, invalid_flags), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(XattrTest, GetXattr) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - int val = 1234; - size_t size = sizeof(val); - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - int buf = 0; - EXPECT_THAT(getxattr(path, name, &buf, size), SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, val); -} - -TEST_F(XattrTest, GetXattrSizeSmallerThanValue) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - std::vector<char> val = {'a', 'a'}; - size_t size = val.size(); - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - char buf = '-'; - EXPECT_THAT(getxattr(path, name, &buf, 1), SyscallFailsWithErrno(ERANGE)); - EXPECT_EQ(buf, '-'); -} - -TEST_F(XattrTest, GetXattrSizeLargerThanValue) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - EXPECT_THAT(setxattr(path, name, &val, 1, /*flags=*/0), SyscallSucceeds()); - - std::vector<char> buf(XATTR_SIZE_MAX); - std::fill(buf.begin(), buf.end(), '-'); - std::vector<char> expected_buf = buf; - expected_buf[0] = 'a'; - EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()), - SyscallSucceedsWithValue(1)); - EXPECT_EQ(buf, expected_buf); -} - -TEST_F(XattrTest, GetXattrZeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - EXPECT_THAT(setxattr(path, name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - - char buf = '-'; - EXPECT_THAT(getxattr(path, name, &buf, 0), - SyscallSucceedsWithValue(sizeof(val))); - EXPECT_EQ(buf, '-'); -} - -TEST_F(XattrTest, GetXattrSizeTooLarge) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - EXPECT_THAT(setxattr(path, name, &val, sizeof(val), /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> buf(XATTR_SIZE_MAX + 1); - std::fill(buf.begin(), buf.end(), '-'); - std::vector<char> expected_buf = buf; - expected_buf[0] = 'a'; - EXPECT_THAT(getxattr(path, name, buf.data(), buf.size()), - SyscallSucceedsWithValue(sizeof(val))); - EXPECT_EQ(buf, expected_buf); -} - -TEST_F(XattrTest, GetXattrNullValue) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - size_t size = sizeof(val); - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - EXPECT_THAT(getxattr(path, name, nullptr, size), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(XattrTest, GetXattrNullValueAndZeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - char val = 'a'; - size_t size = sizeof(val); - // Set value with zero size. - EXPECT_THAT(setxattr(path, name, &val, 0, /*flags=*/0), SyscallSucceeds()); - // Get value with nonzero size. - EXPECT_THAT(getxattr(path, name, nullptr, size), SyscallSucceedsWithValue(0)); - - // Set value with nonzero size. - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - // Get value with zero size. - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallSucceedsWithValue(size)); -} - -TEST_F(XattrTest, GetXattrNonexistentName) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, ListXattr) { - const char* path = test_file_name_.c_str(); - const std::string name = "user.test"; - const std::string name2 = "user.test2"; - const std::string name3 = "user.test3"; - EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name2.c_str(), nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - EXPECT_THAT(setxattr(path, name3.c_str(), nullptr, 0, /*flags=*/0), - SyscallSucceeds()); - - std::vector<char> list(name.size() + 1 + name2.size() + 1 + name3.size() + 1); - char* buf = list.data(); - EXPECT_THAT(listxattr(path, buf, XATTR_SIZE_MAX), - SyscallSucceedsWithValue(list.size())); - - absl::flat_hash_set<std::string> got = {}; - for (char* p = buf; p < buf + list.size(); p += strlen(p) + 1) { - got.insert(std::string{p}); - } - - absl::flat_hash_set<std::string> expected = {name, name2, name3}; - EXPECT_EQ(got, expected); -} - -TEST_F(XattrTest, ListXattrNoXattrs) { - const char* path = test_file_name_.c_str(); - - std::vector<char> list, expected; - EXPECT_THAT(listxattr(path, list.data(), sizeof(list)), - SyscallSucceedsWithValue(0)); - EXPECT_EQ(list, expected); - - // ListXattr should succeed if there are no attributes, even if the buffer - // passed in is a nullptr. - EXPECT_THAT(listxattr(path, nullptr, sizeof(list)), - SyscallSucceedsWithValue(0)); -} - -TEST_F(XattrTest, ListXattrNullBuffer) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - - EXPECT_THAT(listxattr(path, nullptr, sizeof(name)), - SyscallFailsWithErrno(EFAULT)); -} - -TEST_F(XattrTest, ListXattrSizeTooSmall) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - - char list[sizeof(name) - 1]; - EXPECT_THAT(listxattr(path, list, sizeof(list)), - SyscallFailsWithErrno(ERANGE)); -} - -TEST_F(XattrTest, ListXattrZeroSize) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - EXPECT_THAT(listxattr(path, nullptr, 0), - SyscallSucceedsWithValue(sizeof(name))); -} - -TEST_F(XattrTest, RemoveXattr) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(setxattr(path, name, nullptr, 0, /*flags=*/0), SyscallSucceeds()); - EXPECT_THAT(removexattr(path, name), SyscallSucceeds()); - EXPECT_THAT(getxattr(path, name, nullptr, 0), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, RemoveXattrNonexistentName) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, LXattrOnSymlink) { - const char name[] = "user.test"; - TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - TempPath link = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(dir.path(), test_file_name_)); - - EXPECT_THAT(lsetxattr(link.path().c_str(), name, nullptr, 0, 0), - SyscallFailsWithErrno(EPERM)); - EXPECT_THAT(lgetxattr(link.path().c_str(), name, nullptr, 0), - SyscallFailsWithErrno(ENODATA)); - EXPECT_THAT(llistxattr(link.path().c_str(), nullptr, 0), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(lremovexattr(link.path().c_str(), name), - SyscallFailsWithErrno(EPERM)); -} - -TEST_F(XattrTest, LXattrOnNonsymlink) { - const char* path = test_file_name_.c_str(); - const char name[] = "user.test"; - int val = 1234; - size_t size = sizeof(val); - EXPECT_THAT(lsetxattr(path, name, &val, size, /*flags=*/0), - SyscallSucceeds()); - - int buf = 0; - EXPECT_THAT(lgetxattr(path, name, &buf, size), - SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, val); - - char list[sizeof(name)]; - EXPECT_THAT(llistxattr(path, list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - EXPECT_THAT(lremovexattr(path, name), SyscallSucceeds()); -} - -TEST_F(XattrTest, XattrWithFD) { - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), 0)); - const char name[] = "user.test"; - int val = 1234; - size_t size = sizeof(val); - EXPECT_THAT(fsetxattr(fd.get(), name, &val, size, /*flags=*/0), - SyscallSucceeds()); - - int buf = 0; - EXPECT_THAT(fgetxattr(fd.get(), name, &buf, size), - SyscallSucceedsWithValue(size)); - EXPECT_EQ(buf, val); - - char list[sizeof(name)]; - EXPECT_THAT(flistxattr(fd.get(), list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds()); -} - -TEST_F(XattrTest, XattrWithOPath) { - SKIP_IF(IsRunningWithVFS1()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), O_PATH)); - const char name[] = "user.test"; - int val = 1234; - size_t size = sizeof(val); - EXPECT_THAT(fsetxattr(fd.get(), name, &val, size, /*flags=*/0), - SyscallFailsWithErrno(EBADF)); - - int buf; - EXPECT_THAT(fgetxattr(fd.get(), name, &buf, size), - SyscallFailsWithErrno(EBADF)); - - char list[sizeof(name)]; - EXPECT_THAT(flistxattr(fd.get(), list, sizeof(list)), - SyscallFailsWithErrno(EBADF)); - - EXPECT_THAT(fremovexattr(fd.get(), name), SyscallFailsWithErrno(EBADF)); -} - -TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) { - // Trusted namespace not supported in VFS1. - SKIP_IF(IsRunningWithVFS1()); - - // TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_))); - - const char* path = test_file_name_.c_str(); - const char name[] = "trusted.test"; - - // Writing to the trusted.* xattr namespace requires CAP_SYS_ADMIN in the root - // user namespace. There's no easy way to check that, other than trying the - // operation and seeing what happens. We'll call removexattr because it's - // simplest. - if (removexattr(path, name) < 0) { - SKIP_IF(errno == EPERM); - FAIL() << "unexpected errno from removexattr: " << errno; - } - - // Set. - char val = 'a'; - size_t size = sizeof(val); - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds()); - - // Get. - char got = '\0'; - EXPECT_THAT(getxattr(path, name, &got, size), SyscallSucceedsWithValue(size)); - EXPECT_EQ(val, got); - - // List. - char list[sizeof(name)]; - EXPECT_THAT(listxattr(path, list, sizeof(list)), - SyscallSucceedsWithValue(sizeof(name))); - EXPECT_STREQ(list, name); - - // Remove. - EXPECT_THAT(removexattr(path, name), SyscallSucceeds()); - - // Get should now return ENODATA. - EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA)); -} - -TEST_F(XattrTest, TrustedNamespaceWithoutCapSysAdmin) { - // Trusted namespace not supported in VFS1. - SKIP_IF(IsRunningWithVFS1()); - - // TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace. - SKIP_IF(IsRunningOnGvisor() && - !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_))); - - // Drop CAP_SYS_ADMIN if we have it. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) { - EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false)); - } - - const char* path = test_file_name_.c_str(); - const char name[] = "trusted.test"; - - // Set fails. - char val = 'a'; - size_t size = sizeof(val); - EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), - SyscallFailsWithErrno(EPERM)); - - // Get fails. - char got = '\0'; - EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA)); - - // List still works, but returns no items. - char list[sizeof(name)]; - EXPECT_THAT(listxattr(path, list, sizeof(list)), SyscallSucceedsWithValue(0)); - - // Remove fails. - EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/uds/BUILD b/test/uds/BUILD deleted file mode 100644 index a8f49b50c..000000000 --- a/test/uds/BUILD +++ /dev/null @@ -1,17 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -go_library( - name = "uds", - testonly = 1, - srcs = ["uds.go"], - deps = [ - "//pkg/log", - "//pkg/unet", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/test/uds/uds.go b/test/uds/uds.go deleted file mode 100644 index 02a4a7dee..000000000 --- a/test/uds/uds.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package uds contains helpers for testing external UDS functionality. -package uds - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/unet" -) - -// createEchoSocket creates a socket that echoes back anything received. -// -// Only works for stream, seqpacket sockets. -func createEchoSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) - if err != nil { - return nil, fmt.Errorf("error creating echo(%d) socket: %v", protocol, err) - } - - if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { - return nil, fmt.Errorf("error binding echo(%d) socket: %v", protocol, err) - } - - if err := unix.Listen(fd, 0); err != nil { - return nil, fmt.Errorf("error listening echo(%d) socket: %v", protocol, err) - } - - server, err := unet.NewServerSocket(fd) - if err != nil { - return nil, fmt.Errorf("error creating echo(%d) unet socket: %v", protocol, err) - } - - acceptAndEchoOne := func() error { - s, err := server.Accept() - if err != nil { - return fmt.Errorf("failed to accept: %v", err) - } - defer s.Close() - - for { - buf := make([]byte, 512) - for { - n, err := s.Read(buf) - if err == io.EOF { - return nil - } - if err != nil { - return fmt.Errorf("failed to read: %d, %v", n, err) - } - - n, err = s.Write(buf[:n]) - if err != nil { - return fmt.Errorf("failed to write: %d, %v", n, err) - } - } - } - } - - go func() { - for { - if err := acceptAndEchoOne(); err != nil { - log.Warningf("Failed to handle echo(%d) socket: %v", protocol, err) - return - } - } - }() - - cleanup = func() { - if err := server.Close(); err != nil { - log.Warningf("Failed to close echo(%d) socket: %v", protocol, err) - } - } - - return cleanup, nil -} - -// createNonListeningSocket creates a socket that is bound but not listening. -// -// Only relevant for stream, seqpacket sockets. -func createNonListeningSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) - if err != nil { - return nil, fmt.Errorf("error creating nonlistening(%d) socket: %v", protocol, err) - } - - if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { - return nil, fmt.Errorf("error binding nonlistening(%d) socket: %v", protocol, err) - } - - cleanup = func() { - if err := unix.Close(fd); err != nil { - log.Warningf("Failed to close nonlistening(%d) socket: %v", protocol, err) - } - } - - return cleanup, nil -} - -// createNullSocket creates a socket that reads anything received. -// -// Only works for dgram sockets. -func createNullSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) - if err != nil { - return nil, fmt.Errorf("error creating null(%d) socket: %v", protocol, err) - } - - if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { - return nil, fmt.Errorf("error binding null(%d) socket: %v", protocol, err) - } - - s, err := unet.NewSocket(fd) - if err != nil { - return nil, fmt.Errorf("error creating null(%d) unet socket: %v", protocol, err) - } - - go func() { - buf := make([]byte, 512) - for { - n, err := s.Read(buf) - if err != nil { - log.Warningf("failed to read: %d, %v", n, err) - return - } - } - }() - - cleanup = func() { - if err := s.Close(); err != nil { - log.Warningf("Failed to close null(%d) socket: %v", protocol, err) - } - } - - return cleanup, nil -} - -type socketCreator func(path string, proto int) (cleanup func(), err error) - -// CreateSocketTree creates a local tree of unix domain sockets for use in -// testing: -// * /stream/echo -// * /stream/nonlistening -// * /seqpacket/echo -// * /seqpacket/nonlistening -// * /dgram/null -func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) { - dir, err = ioutil.TempDir(baseDir, "sockets") - if err != nil { - return "", nil, fmt.Errorf("error creating temp dir: %v", err) - } - - var protocols = []struct { - protocol int - name string - sockets map[string]socketCreator - }{ - { - protocol: unix.SOCK_STREAM, - name: "stream", - sockets: map[string]socketCreator{ - "echo": createEchoSocket, - "nonlistening": createNonListeningSocket, - }, - }, - { - protocol: unix.SOCK_SEQPACKET, - name: "seqpacket", - sockets: map[string]socketCreator{ - "echo": createEchoSocket, - "nonlistening": createNonListeningSocket, - }, - }, - { - protocol: unix.SOCK_DGRAM, - name: "dgram", - sockets: map[string]socketCreator{ - "null": createNullSocket, - }, - }, - } - - var cleanups []func() - for _, proto := range protocols { - protoDir := filepath.Join(dir, proto.name) - if err := os.Mkdir(protoDir, 0755); err != nil { - return "", nil, fmt.Errorf("error creating %s dir: %v", proto.name, err) - } - - for name, fn := range proto.sockets { - path := filepath.Join(protoDir, name) - cleanup, err := fn(path, proto.protocol) - if err != nil { - return "", nil, fmt.Errorf("error creating %s %s socket: %v", proto.name, name, err) - } - - cleanups = append(cleanups, cleanup) - } - } - - cleanup = func() { - for _, c := range cleanups { - c() - } - - os.RemoveAll(dir) - } - - return dir, cleanup, nil -} diff --git a/test/util/BUILD b/test/util/BUILD deleted file mode 100644 index e561f3daa..000000000 --- a/test/util/BUILD +++ /dev/null @@ -1,370 +0,0 @@ -load("//tools:defs.bzl", "cc_library", "cc_test", "coreutil", "gbenchmark", "gtest", "select_system") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -cc_library( - name = "capability_util", - testonly = 1, - srcs = ["capability_util.cc"], - hdrs = ["capability_util.h"], - deps = [ - ":cleanup", - ":memory_util", - ":posix_error", - ":save_util", - ":test_util", - "@com_google_absl//absl/strings", - ], -) - -cc_library( - name = "eventfd_util", - testonly = 1, - hdrs = ["eventfd_util.h"], - deps = [ - ":file_descriptor", - ":posix_error", - ":save_util", - ], -) - -cc_library( - name = "file_descriptor", - testonly = 1, - hdrs = ["file_descriptor.h"], - deps = [ - ":logging", - ":posix_error", - ":save_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - gtest, - ], -) - -cc_library( - name = "fuse_util", - testonly = 1, - srcs = ["fuse_util.cc"], - hdrs = ["fuse_util.h"], -) - -cc_library( - name = "proc_util", - testonly = 1, - srcs = ["proc_util.cc"], - hdrs = ["proc_util.h"], - deps = [ - ":fs_util", - ":posix_error", - ":test_util", - "@com_google_absl//absl/strings", - gtest, - ], -) - -cc_test( - name = "proc_util_test", - size = "small", - srcs = ["proc_util_test.cc"], - deps = [ - ":proc_util", - ":test_main", - ":test_util", - gtest, - ], -) - -cc_library( - name = "cleanup", - testonly = 1, - hdrs = ["cleanup.h"], -) - -cc_library( - name = "fs_util", - testonly = 1, - srcs = ["fs_util.cc"], - hdrs = ["fs_util.h"], - deps = [ - ":cleanup", - ":file_descriptor", - ":posix_error", - "@com_google_absl//absl/strings", - gtest, - ], -) - -cc_test( - name = "fs_util_test", - size = "small", - srcs = ["fs_util_test.cc"], - deps = [ - ":fs_util", - ":posix_error", - ":temp_path", - ":test_main", - ":test_util", - gtest, - ], -) - -cc_library( - name = "logging", - testonly = 1, - srcs = ["logging.cc"], - hdrs = ["logging.h"], -) - -cc_library( - name = "memory_util", - testonly = 1, - hdrs = ["memory_util.h"], - deps = [ - ":logging", - ":posix_error", - ":save_util", - ":test_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - ], -) - -cc_library( - name = "mount_util", - testonly = 1, - hdrs = ["mount_util.h"], - deps = [ - ":cleanup", - ":posix_error", - ":test_util", - gtest, - ], -) - -cc_library( - name = "save_util", - testonly = 1, - srcs = [ - "save_util.cc", - "save_util_linux.cc", - "save_util_other.cc", - ], - hdrs = ["save_util.h"], - defines = select_system(), - deps = [ - ":logging", - "@com_google_absl//absl/types:optional", - ], -) - -cc_library( - name = "multiprocess_util", - testonly = 1, - srcs = ["multiprocess_util.cc"], - hdrs = ["multiprocess_util.h"], - deps = [ - ":cleanup", - ":file_descriptor", - ":posix_error", - ":save_util", - ":test_util", - gtest, - "@com_google_absl//absl/strings", - ], -) - -cc_library( - name = "platform_util", - testonly = 1, - srcs = ["platform_util.cc"], - hdrs = ["platform_util.h"], - deps = [":test_util"], -) - -cc_library( - name = "posix_error", - testonly = 1, - srcs = ["posix_error.cc"], - hdrs = ["posix_error.h"], - deps = [ - ":logging", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:variant", - gtest, - ], -) - -cc_test( - name = "posix_error_test", - size = "small", - srcs = ["posix_error_test.cc"], - deps = [ - ":posix_error", - ":test_main", - gtest, - ], -) - -cc_library( - name = "pty_util", - testonly = 1, - srcs = ["pty_util.cc"], - hdrs = ["pty_util.h"], - deps = [ - ":file_descriptor", - ":posix_error", - ], -) - -cc_library( - name = "signal_util", - testonly = 1, - srcs = ["signal_util.cc"], - hdrs = ["signal_util.h"], - deps = [ - ":cleanup", - ":posix_error", - ":test_util", - gtest, - ], -) - -cc_library( - name = "temp_path", - testonly = 1, - srcs = ["temp_path.cc"], - hdrs = ["temp_path.h"], - deps = [ - ":fs_util", - ":posix_error", - ":test_util", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - gtest, - ], -) - -cc_library( - name = "test_util", - testonly = 1, - srcs = [ - "test_util.cc", - "test_util_impl.cc", - "test_util_runfiles.cc", - ], - hdrs = ["test_util.h"], - defines = select_system(), - deps = coreutil() + [ - ":fs_util", - ":logging", - ":posix_error", - ":save_util", - "@bazel_tools//tools/cpp/runfiles", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/flags:parse", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/time", - gtest, - gbenchmark, - ], -) - -cc_library( - name = "thread_util", - testonly = 1, - hdrs = ["thread_util.h"], - deps = [":logging"], -) - -cc_library( - name = "time_util", - testonly = 1, - srcs = ["time_util.cc"], - hdrs = ["time_util.h"], - deps = [ - "@com_google_absl//absl/time", - ], -) - -cc_library( - name = "timer_util", - testonly = 1, - srcs = ["timer_util.cc"], - hdrs = ["timer_util.h"], - deps = [ - ":cleanup", - ":logging", - ":posix_error", - ":test_util", - "@com_google_absl//absl/time", - gtest, - ], -) - -cc_test( - name = "test_util_test", - size = "small", - srcs = ["test_util_test.cc"], - deps = [ - ":test_main", - ":test_util", - gtest, - ], -) - -cc_library( - name = "test_main", - testonly = 1, - srcs = ["test_main.cc"], - deps = [":test_util"], -) - -cc_library( - name = "epoll_util", - testonly = 1, - srcs = ["epoll_util.cc"], - hdrs = ["epoll_util.h"], - deps = [ - ":file_descriptor", - ":posix_error", - ":save_util", - gtest, - ], -) - -cc_library( - name = "rlimit_util", - testonly = 1, - srcs = ["rlimit_util.cc"], - hdrs = ["rlimit_util.h"], - deps = [ - ":cleanup", - ":logging", - ":posix_error", - ":test_util", - ], -) - -cc_library( - name = "uid_util", - testonly = 1, - srcs = ["uid_util.cc"], - hdrs = ["uid_util.h"], - deps = [ - ":posix_error", - ":save_util", - ], -) - -cc_library( - name = "temp_umask", - testonly = 1, - hdrs = ["temp_umask.h"], -) diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc deleted file mode 100644 index a1b994c45..000000000 --- a/test/util/capability_util.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/capability_util.h" - -#include <linux/capability.h> -#include <sched.h> -#include <sys/mman.h> -#include <sys/wait.h> - -#include <iostream> - -#include "absl/strings/str_cat.h" -#include "test/util/memory_util.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<bool> CanCreateUserNamespace() { - // The most reliable way to determine if userns creation is possible is by - // trying to create one; see below. - ASSIGN_OR_RETURN_ERRNO( - auto child_stack, - MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); - int const child_pid = clone( - +[](void*) { return 0; }, - reinterpret_cast<void*>(child_stack.addr() + kPageSize), - CLONE_NEWUSER | SIGCHLD, /* arg = */ nullptr); - if (child_pid > 0) { - int status; - int const ret = waitpid(child_pid, &status, /* options = */ 0); - MaybeSave(); - if (ret < 0) { - return PosixError(errno, "waitpid"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - return PosixError( - ESRCH, absl::StrCat("child process exited with status ", status)); - } - return true; - } else if (errno == EPERM) { - // Per clone(2), EPERM can be returned if: - // - // - "CLONE_NEWUSER was specified in flags, but either the effective user ID - // or the effective group ID of the caller does not have a mapping in the - // parent namespace (see user_namespaces(7))." - // - // - "(since Linux 3.9) CLONE_NEWUSER was specified in flags and the caller - // is in a chroot environment (i.e., the caller's root directory does - // not match the root directory of the mount namespace in which it - // resides)." - std::cerr << "clone(CLONE_NEWUSER) failed with EPERM" << std::endl; - return false; - } else if (errno == EUSERS) { - // "(since Linux 3.11) CLONE_NEWUSER was specified in flags, and the call - // would cause the limit on the number of nested user namespaces to be - // exceeded. See user_namespaces(7)." - std::cerr << "clone(CLONE_NEWUSER) failed with EUSERS" << std::endl; - return false; - } else { - // Unexpected error code; indicate an actual error. - return PosixError(errno, "clone(CLONE_NEWUSER)"); - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/capability_util.h b/test/util/capability_util.h deleted file mode 100644 index a03bc7e05..000000000 --- a/test/util/capability_util.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Utilities for testing capabilities. - -#ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ -#define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ - -#include <errno.h> -#include <linux/capability.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -#ifndef _LINUX_CAPABILITY_VERSION_3 -#error Expecting _LINUX_CAPABILITY_VERSION_3 support -#endif - -namespace gvisor { -namespace testing { - -// HaveCapability returns true if the process has the specified EFFECTIVE -// capability. -inline PosixErrorOr<bool> HaveCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; -} - -// SetCapability sets the specified EFFECTIVE capability. -inline PosixError SetCapability(int cap, bool set) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - if (set) { - caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); - } else { - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - } - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE -// capabilities must be a subset of PERMITTED, so those are dropped as well. -inline PosixError DropPermittedCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); - - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -PosixErrorOr<bool> CanCreateUserNamespace(); - -class AutoCapability { - public: - AutoCapability(int cap, bool set) : cap_(cap), set_(set) { - EXPECT_NO_ERRNO(SetCapability(cap_, set_)); - } - - ~AutoCapability() { EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); } - - private: - int cap_; - bool set_; -}; - -} // namespace testing -} // namespace gvisor -#endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ diff --git a/test/util/cleanup.h b/test/util/cleanup.h deleted file mode 100644 index c76482ef4..000000000 --- a/test/util/cleanup.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_CLEANUP_H_ -#define GVISOR_TEST_UTIL_CLEANUP_H_ - -#include <functional> -#include <utility> - -namespace gvisor { -namespace testing { - -class Cleanup { - public: - Cleanup() : released_(true) {} - explicit Cleanup(std::function<void()>&& callback) : cb_(callback) {} - - Cleanup(Cleanup&& other) { - released_ = other.released_; - cb_ = other.Release(); - } - - Cleanup& operator=(Cleanup&& other) { - released_ = other.released_; - cb_ = other.Release(); - return *this; - } - - ~Cleanup() { - if (!released_) { - cb_(); - } - } - - std::function<void()>&& Release() { - released_ = true; - return std::move(cb_); - } - - private: - Cleanup(Cleanup const& other) = delete; - - bool released_ = false; - std::function<void(void)> cb_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_CLEANUP_H_ diff --git a/test/util/epoll_util.cc b/test/util/epoll_util.cc deleted file mode 100644 index 2e5051468..000000000 --- a/test/util/epoll_util.cc +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/epoll_util.h" - -#include <sys/epoll.h> - -#include "gmock/gmock.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<FileDescriptor> NewEpollFD(int size) { - // "Since Linux 2.6.8, the size argument is ignored, but must be greater than - // zero." - epoll_create(2) - int fd = epoll_create(size); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, "epoll_create"); - } - return FileDescriptor(fd); -} - -PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events, - uint64_t data) { - struct epoll_event event; - event.events = events; - event.data.u64 = data; - int rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, target_fd, &event); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "epoll_ctl"); - } - return NoError(); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/epoll_util.h b/test/util/epoll_util.h deleted file mode 100644 index f233b37d5..000000000 --- a/test/util/epoll_util.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_EPOLL_UTIL_H_ -#define GVISOR_TEST_UTIL_EPOLL_UTIL_H_ - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Returns a new epoll file descriptor. -PosixErrorOr<FileDescriptor> NewEpollFD(int size = 1); - -// Registers `target_fd` with the epoll instance represented by `epoll_fd` for -// the epoll events `events`. Events on `target_fd` will be indicated by setting -// data.u64 to `data` in the returned epoll_event. -PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events, - uint64_t data); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_EPOLL_UTIL_H_ diff --git a/test/util/eventfd_util.h b/test/util/eventfd_util.h deleted file mode 100644 index cb9ce829c..000000000 --- a/test/util/eventfd_util.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_EVENTFD_UTIL_H_ -#define GVISOR_TEST_UTIL_EVENTFD_UTIL_H_ - -#include <sys/eventfd.h> - -#include <cerrno> - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" - -namespace gvisor { -namespace testing { - -// Returns a new eventfd with the given initial value and flags. -inline PosixErrorOr<FileDescriptor> NewEventFD(unsigned int initval = 0, - int flags = 0) { - int fd = eventfd(initval, flags); - MaybeSave(); - if (fd < 0) { - return PosixError(errno, "eventfd"); - } - return FileDescriptor(fd); -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_EVENTFD_UTIL_H_ diff --git a/test/util/file_descriptor.h b/test/util/file_descriptor.h deleted file mode 100644 index fc5caa55b..000000000 --- a/test/util/file_descriptor.h +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ -#define GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ - -#include <fcntl.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <algorithm> -#include <string> - -#include "gmock/gmock.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" - -namespace gvisor { -namespace testing { - -// FileDescriptor is an RAII type class which takes ownership of a file -// descriptor. It will close the FD when this object goes out of scope. -class FileDescriptor { - public: - // Constructs an empty FileDescriptor (one that does not own a file - // descriptor). - FileDescriptor() = default; - - // Constructs a FileDescriptor that owns fd. If fd is negative, constructs an - // empty FileDescriptor. - explicit FileDescriptor(int fd) { set_fd(fd); } - - FileDescriptor(FileDescriptor&& orig) : fd_(orig.release()) {} - - FileDescriptor& operator=(FileDescriptor&& orig) { - reset(orig.release()); - return *this; - } - - PosixErrorOr<FileDescriptor> Dup() const { - if (fd_ < 0) { - return PosixError(EINVAL, "Attempting to Dup unset fd"); - } - - int fd = dup(fd_); - if (fd < 0) { - return PosixError(errno, absl::StrCat("dup ", fd_)); - } - MaybeSave(); - return FileDescriptor(fd); - } - - FileDescriptor(FileDescriptor const& other) = delete; - FileDescriptor& operator=(FileDescriptor const& other) = delete; - - ~FileDescriptor() { reset(); } - - // If this object is non-empty, returns the owned file descriptor. (Ownership - // is retained by the FileDescriptor.) Otherwise returns -1. - int get() const { return fd_; } - - // If this object is non-empty, transfers ownership of the file descriptor to - // the caller and returns it. Otherwise returns -1. - int release() { - int const fd = fd_; - fd_ = -1; - return fd; - } - - // If this object is non-empty, closes the owned file descriptor (recording a - // test failure if the close fails). - void reset() { reset(-1); } - - // Like no-arg reset(), but the FileDescriptor takes ownership of fd after - // closing its existing file descriptor. - void reset(int fd) { - if (fd_ >= 0) { - TEST_PCHECK(close(fd_) == 0); - MaybeSave(); - } - set_fd(fd); - } - - private: - // Wrapper that coerces negative fd values other than -1 to -1 so that get() - // etc. return -1. - void set_fd(int fd) { fd_ = std::max(fd, -1); } - - int fd_ = -1; -}; - -// Wrapper around open(2) that returns a FileDescriptor. -inline PosixErrorOr<FileDescriptor> Open(std::string const& path, int flags, - mode_t mode = 0) { - int fd = open(path.c_str(), flags, mode); - if (fd < 0) { - return PosixError(errno, absl::StrFormat("open(%s, %#x, %#o)", path.c_str(), - flags, mode)); - } - MaybeSave(); - return FileDescriptor(fd); -} - -// Wrapper around openat(2) that returns a FileDescriptor. -inline PosixErrorOr<FileDescriptor> OpenAt(int dirfd, std::string const& path, - int flags, mode_t mode = 0) { - int fd = openat(dirfd, path.c_str(), flags, mode); - if (fd < 0) { - return PosixError(errno, absl::StrFormat("openat(%d, %s, %#x, %#o)", dirfd, - path, flags, mode)); - } - MaybeSave(); - return FileDescriptor(fd); -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc deleted file mode 100644 index 5f1ce0d8a..000000000 --- a/test/util/fs_util.cc +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/fs_util.h" - -#include <dirent.h> -#ifdef __linux__ -#include <linux/magic.h> -#endif // __linux__ -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include "gmock/gmock.h" -#include "absl/strings/match.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -namespace { -PosixError WriteContentsToFD(int fd, absl::string_view contents) { - int written = 0; - while (static_cast<absl::string_view::size_type>(written) < contents.size()) { - int wrote = write(fd, contents.data() + written, contents.size() - written); - if (wrote < 0) { - if (errno == EINTR) { - continue; - } - return PosixError( - errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure.")); - } - written += wrote; - } - return NoError(); -} -} // namespace - -namespace internal { - -// Given a collection of file paths, append them all together, -// ensuring that the proper path separators are inserted between them. -std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) { - std::string result; - - if (paths.size() != 0) { - // This size calculation is worst-case: it assumes one extra "/" for every - // path other than the first. - size_t total_size = paths.size() - 1; - for (const absl::string_view path : paths) total_size += path.size(); - result.resize(total_size); - - auto begin = result.begin(); - auto out = begin; - bool trailing_slash = false; - for (absl::string_view path : paths) { - if (path.empty()) continue; - if (path.front() == '/') { - if (trailing_slash) { - path.remove_prefix(1); - } - } else { - if (!trailing_slash && out != begin) *out++ = '/'; - } - const size_t this_size = path.size(); - memcpy(&*out, path.data(), this_size); - out += this_size; - trailing_slash = out[-1] == '/'; - } - result.erase(out - begin); - } - return result; -} -} // namespace internal - -// Returns a status or the current working directory. -PosixErrorOr<std::string> GetCWD() { - char buffer[PATH_MAX + 1] = {}; - if (getcwd(buffer, PATH_MAX) == nullptr) { - return PosixError(errno, "GetCWD() failed"); - } - - return std::string(buffer); -} - -PosixErrorOr<struct stat> Stat(absl::string_view path) { - struct stat stat_buf; - int res = stat(std::string(path).c_str(), &stat_buf); - if (res < 0) { - return PosixError(errno, absl::StrCat("stat ", path)); - } - return stat_buf; -} - -PosixErrorOr<struct stat> Lstat(absl::string_view path) { - struct stat stat_buf; - int res = lstat(std::string(path).c_str(), &stat_buf); - if (res < 0) { - return PosixError(errno, absl::StrCat("lstat ", path)); - } - return stat_buf; -} - -PosixErrorOr<struct stat> Fstat(int fd) { - struct stat stat_buf; - int res = fstat(fd, &stat_buf); - if (res < 0) { - return PosixError(errno, absl::StrCat("fstat ", fd)); - } - return stat_buf; -} - -PosixErrorOr<bool> Exists(absl::string_view path) { - struct stat stat_buf; - int res = lstat(std::string(path).c_str(), &stat_buf); - if (res < 0) { - if (errno == ENOENT) { - return false; - } - return PosixError(errno, absl::StrCat("lstat ", path)); - } - return true; -} - -PosixErrorOr<bool> IsDirectory(absl::string_view path) { - ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Lstat(path)); - if (S_ISDIR(stat_buf.st_mode)) { - return true; - } - - return false; -} - -PosixError Delete(absl::string_view path) { - int res = unlink(std::string(path).c_str()); - if (res < 0) { - return PosixError(errno, absl::StrCat("unlink ", path)); - } - - return NoError(); -} - -PosixError Truncate(absl::string_view path, int length) { - int res = truncate(std::string(path).c_str(), length); - if (res < 0) { - return PosixError(errno, - absl::StrCat("truncate ", path, " to length ", length)); - } - - return NoError(); -} - -PosixError Chmod(absl::string_view path, int mode) { - int res = chmod(std::string(path).c_str(), mode); - if (res < 0) { - return PosixError(errno, absl::StrCat("chmod ", path)); - } - - return NoError(); -} - -PosixError MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode, - dev_t dev) { - int res = mknodat(dfd.get(), std::string(path).c_str(), mode, dev); - if (res < 0) { - return PosixError(errno, absl::StrCat("mknod ", path)); - } - - return NoError(); -} - -PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, - int flags) { - int res = unlinkat(dfd.get(), std::string(path).c_str(), flags); - if (res < 0) { - return PosixError(errno, absl::StrCat("unlink ", path)); - } - - return NoError(); -} - -PosixError Mkdir(absl::string_view path, int mode) { - int res = mkdir(std::string(path).c_str(), mode); - if (res < 0) { - return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); - } - - return NoError(); -} - -PosixError Rmdir(absl::string_view path) { - int res = rmdir(std::string(path).c_str()); - if (res < 0) { - return PosixError(errno, absl::StrCat("rmdir ", path)); - } - - return NoError(); -} - -PosixError SetContents(absl::string_view path, absl::string_view contents) { - ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); - if (!exists) { - return PosixError( - ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist.")); - } - - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC)); - return WriteContentsToFD(fd.get(), contents); -} - -// Create a file with the given contents (if it does not already exist with the -// given mode) and then set the contents. -PosixError CreateWithContents(absl::string_view path, - absl::string_view contents, int mode) { - ASSIGN_OR_RETURN_ERRNO( - auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode)); - return WriteContentsToFD(fd.get(), contents); -} - -PosixError GetContents(absl::string_view path, std::string* output) { - ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY)); - output->clear(); - - // Keep reading until we hit an EOF or an error. - return GetContentsFD(fd.get(), output); -} - -PosixErrorOr<std::string> GetContents(absl::string_view path) { - std::string ret; - RETURN_IF_ERRNO(GetContents(path, &ret)); - return ret; -} - -PosixErrorOr<std::string> GetContentsFD(int fd) { - std::string ret; - RETURN_IF_ERRNO(GetContentsFD(fd, &ret)); - return ret; -} - -PosixError GetContentsFD(int fd, std::string* output) { - // Keep reading until we hit an EOF or an error. - while (true) { - char buf[16 * 1024] = {}; // Read in 16KB chunks. - int bytes_read = read(fd, buf, sizeof(buf)); - if (bytes_read < 0) { - if (errno == EINTR) { - continue; - } - return PosixError(errno, "GetContentsFD read failure."); - } - - if (bytes_read == 0) { - break; // EOF. - } - - output->append(buf, bytes_read); - } - return NoError(); -} - -PosixErrorOr<std::string> ReadLink(absl::string_view path) { - char buf[PATH_MAX + 1] = {}; - int ret = readlink(std::string(path).c_str(), buf, PATH_MAX); - if (ret < 0) { - return PosixError(errno, absl::StrCat("readlink ", path)); - } - - return std::string(buf, ret); -} - -PosixError WalkTree( - absl::string_view path, bool recursive, - const std::function<void(absl::string_view, const struct stat&)>& cb) { - DIR* dir = opendir(std::string(path).c_str()); - if (dir == nullptr) { - return PosixError(errno, absl::StrCat("opendir ", path)); - } - auto dir_closer = Cleanup([&dir]() { closedir(dir); }); - while (true) { - // Readdir(3): If the end of the directory stream is reached, NULL is - // returned and errno is not changed. If an error occurs, NULL is returned - // and errno is set appropriately. To distinguish end of stream and from an - // error, set errno to zero before calling readdir() and then check the - // value of errno if NULL is returned. - errno = 0; - struct dirent* dp = readdir(dir); - if (dp == nullptr) { - if (errno != 0) { - return PosixError(errno, absl::StrCat("readdir ", path)); - } - break; // We're done. - } - - if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { - // Skip dots. - continue; - } - - auto full_path = JoinPath(path, dp->d_name); - ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path)); - if (S_ISDIR(s.st_mode) && recursive) { - RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb)); - } else { - cb(full_path, s); - } - } - // We're done walking so let's invoke our cleanup callback now. - dir_closer.Release()(); - - // And we have to dispatch the callback on the base directory. - ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path)); - cb(path, s); - - return NoError(); -} - -PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, - bool skipdots) { - std::vector<std::string> files; - - DIR* dir = opendir(std::string(abspath).c_str()); - if (dir == nullptr) { - return PosixError(errno, absl::StrCat("opendir ", abspath)); - } - auto dir_closer = Cleanup([&dir]() { closedir(dir); }); - while (true) { - // Readdir(3): If the end of the directory stream is reached, NULL is - // returned and errno is not changed. If an error occurs, NULL is returned - // and errno is set appropriately. To distinguish end of stream and from an - // error, set errno to zero before calling readdir() and then check the - // value of errno if NULL is returned. - errno = 0; - struct dirent* dp = readdir(dir); - if (dp == nullptr) { - if (errno != 0) { - return PosixError(errno, absl::StrCat("readdir ", abspath)); - } - break; // We're done. - } - - if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { - if (skipdots) { - continue; - } - } - files.push_back(std::string(dp->d_name)); - } - - return files; -} - -PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, - int* undeleted_files) { - ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); - if (!exists) { - return PosixError(ENOENT, absl::StrCat(path, " does not exist")); - } - - ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path)); - if (!dir) { - // Nothing recursive needs to happen we can just call Delete. - auto status = Delete(path); - if (!status.ok() && undeleted_files) { - (*undeleted_files)++; - } - return status; - } - - return WalkTree(path, /*recursive=*/true, - [&](absl::string_view absolute_path, const struct stat& s) { - if (S_ISDIR(s.st_mode)) { - auto rm_status = Rmdir(absolute_path); - if (!rm_status.ok() && undeleted_dirs) { - (*undeleted_dirs)++; - } - } else { - auto delete_status = Delete(absolute_path); - if (!delete_status.ok() && undeleted_files) { - (*undeleted_files)++; - } - } - }); -} - -PosixError RecursivelyCreateDir(absl::string_view path) { - if (path.empty() || path == "/") { - return PosixError(EINVAL, "Cannot create root!"); - } - - // Does it already exist, if so we're done. - ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); - if (exists) { - return NoError(); - } - - // Do we need to create directories under us? - auto dirname = Dirname(path); - ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname)); - if (!exists) { - RETURN_IF_ERRNO(RecursivelyCreateDir(dirname)); - } - - return Mkdir(path); -} - -// Makes a path absolute with respect to an optional base. If no base is -// provided it will use the current working directory. -PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, - absl::string_view base) { - if (filename.empty()) { - return PosixError(EINVAL, "filename cannot be empty."); - } - - if (filename[0] == '/') { - // This path is already absolute. - return std::string(filename); - } - - std::string actual_base; - if (!base.empty()) { - actual_base = std::string(base); - } else { - auto cwd_or = GetCWD(); - RETURN_IF_ERRNO(cwd_or.error()); - actual_base = cwd_or.ValueOrDie(); - } - - // Reverse iterate removing trailing slashes, effectively right trim '/'. - for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) { - actual_base.erase(i, 1); - } - - if (filename == ".") { - return actual_base.empty() ? "/" : actual_base; - } - - return absl::StrCat(actual_base, "/", filename); -} - -std::string CleanPath(const absl::string_view unclean_path) { - std::string path = std::string(unclean_path); - const char* src = path.c_str(); - std::string::iterator dst = path.begin(); - - // Check for absolute path and determine initial backtrack limit. - const bool is_absolute_path = *src == '/'; - if (is_absolute_path) { - *dst++ = *src++; - while (*src == '/') ++src; - } - std::string::const_iterator backtrack_limit = dst; - - // Process all parts - while (*src) { - bool parsed = false; - - if (src[0] == '.') { - // 1dot ".<whateverisnext>", check for END or SEP. - if (src[1] == '/' || !src[1]) { - if (*++src) { - ++src; - } - parsed = true; - } else if (src[1] == '.' && (src[2] == '/' || !src[2])) { - // 2dot END or SEP (".." | "../<whateverisnext>"). - src += 2; - if (dst != backtrack_limit) { - // We can backtrack the previous part - for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) { - // Empty. - } - } else if (!is_absolute_path) { - // Failed to backtrack and we can't skip it either. Rewind and copy. - src -= 2; - *dst++ = *src++; - *dst++ = *src++; - if (*src) { - *dst++ = *src; - } - // We can never backtrack over a copied "../" part so set new limit. - backtrack_limit = dst; - } - if (*src) { - ++src; - } - parsed = true; - } - } - - // If not parsed, copy entire part until the next SEP or EOS. - if (!parsed) { - while (*src && *src != '/') { - *dst++ = *src++; - } - if (*src) { - *dst++ = *src++; - } - } - - // Skip consecutive SEP occurrences - while (*src == '/') { - ++src; - } - } - - // Calculate and check the length of the cleaned path. - int path_length = dst - path.begin(); - if (path_length != 0) { - // Remove trailing '/' except if it is root path ("/" ==> path_length := 1) - if (path_length > 1 && path[path_length - 1] == '/') { - --path_length; - } - path.resize(path_length); - } else { - // The cleaned path is empty; assign "." as per the spec. - path.assign(1, '.'); - } - return path; -} - -PosixErrorOr<std::string> GetRelativePath(absl::string_view source, - absl::string_view dest) { - if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) { - // At least one of the inputs is not an absolute path. - return PosixError( - EINVAL, - "GetRelativePath: At least one of the inputs is not an absolute path."); - } - const std::string clean_source = CleanPath(source); - const std::string clean_dest = CleanPath(dest); - auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty()); - auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty()); - auto source_iter = source_parts.begin(); - auto dest_iter = dest_parts.begin(); - - // Advance past common prefix. - while (source_iter != source_parts.end() && dest_iter != dest_parts.end() && - *source_iter == *dest_iter) { - ++source_iter; - ++dest_iter; - } - - // Build result backtracking. - std::string result = ""; - while (source_iter != source_parts.end()) { - absl::StrAppend(&result, "../"); - ++source_iter; - } - - // Add remaining path to dest. - while (dest_iter != dest_parts.end()) { - absl::StrAppend(&result, *dest_iter, "/"); - ++dest_iter; - } - - if (result.empty()) { - return std::string("."); - } - - // Remove trailing slash. - result.erase(result.size() - 1); - return result; -} - -absl::string_view Dirname(absl::string_view path) { - return SplitPath(path).first; -} - -absl::string_view Basename(absl::string_view path) { - return SplitPath(path).second; -} - -std::pair<absl::string_view, absl::string_view> SplitPath( - absl::string_view path) { - std::string::size_type pos = path.find_last_of('/'); - - // Handle the case with no '/' in 'path'. - if (pos == absl::string_view::npos) { - return std::make_pair(path.substr(0, 0), path); - } - - // Handle the case with a single leading '/' in 'path'. - if (pos == 0) { - return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1)); - } - - return std::make_pair(path.substr(0, pos), - absl::ClippedSubstr(path, pos + 1)); -} - -std::string JoinPath(absl::string_view path1, absl::string_view path2) { - if (path1.empty()) { - return std::string(path2); - } - if (path2.empty()) { - return std::string(path1); - } - - if (path1.back() == '/') { - if (path2.front() == '/') { - return absl::StrCat(path1, absl::ClippedSubstr(path2, 1)); - } - } else { - if (path2.front() != '/') { - return absl::StrCat(path1, "/", path2); - } - } - return absl::StrCat(path1, path2); -} - -PosixErrorOr<std::string> ProcessExePath(int pid) { - if (pid <= 0) { - return PosixError(EINVAL, "Invalid pid specified"); - } - - return ReadLink(absl::StrCat("/proc/", pid, "/exe")); -} - -#ifdef __linux__ -PosixErrorOr<bool> IsTmpfs(const std::string& path) { - struct statfs stat; - if (statfs(path.c_str(), &stat)) { - if (errno == ENOENT) { - // Nothing at path, don't raise this as an error. Instead, just report no - // tmpfs at path. - return false; - } - return PosixError(errno, - absl::StrFormat("statfs(\"%s\", %#p)", path, &stat)); - } - return stat.f_type == TMPFS_MAGIC; -} -#endif // __linux__ - -PosixErrorOr<bool> IsOverlayfs(const std::string& path) { - struct statfs stat; - if (statfs(path.c_str(), &stat)) { - if (errno == ENOENT) { - // Nothing at path, don't raise this as an error. Instead, just report no - // overlayfs at path. - return false; - } - return PosixError(errno, - absl::StrFormat("statfs(\"%s\", %#p)", path, &stat)); - } - return stat.f_type == OVERLAYFS_SUPER_MAGIC; -} - -PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) { - struct stat stat_result1, stat_result2; - int res = fstat(fd1.get(), &stat_result1); - if (res < 0) { - return PosixError(errno, absl::StrCat("fstat ", fd1.get())); - } - - res = fstat(fd2.get(), &stat_result2); - if (res < 0) { - return PosixError(errno, absl::StrCat("fstat ", fd2.get())); - } - EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev); - EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino); - - return NoError(); -} -} // namespace testing -} // namespace gvisor diff --git a/test/util/fs_util.h b/test/util/fs_util.h deleted file mode 100644 index 2190c3bca..000000000 --- a/test/util/fs_util.h +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_FS_UTIL_H_ -#define GVISOR_TEST_UTIL_FS_UTIL_H_ - -#include <dirent.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include "absl/strings/string_view.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// O_LARGEFILE as defined by Linux. glibc tries to be clever by setting it to 0 -// because "it isn't needed", even though Linux can return it via F_GETFL. -#if defined(__x86_64__) -constexpr int kOLargeFile = 00100000; -#elif defined(__aarch64__) -constexpr int kOLargeFile = 00400000; -#else -#error "Unknown architecture" -#endif - -// From linux/magic.h. For some reason, not defined in the headers for some -// build environments. -#define OVERLAYFS_SUPER_MAGIC 0x794c7630 - -// Returns a status or the current working directory. -PosixErrorOr<std::string> GetCWD(); - -// Returns true/false depending on whether or not path exists, or an error if it -// can't be determined. -PosixErrorOr<bool> Exists(absl::string_view path); - -// Returns a stat structure for the given path or an error. If the path -// represents a symlink, it will be traversed. -PosixErrorOr<struct stat> Stat(absl::string_view path); - -// Returns a stat structure for the given path or an error. If the path -// represents a symlink, it will not be traversed. -PosixErrorOr<struct stat> Lstat(absl::string_view path); - -// Returns a stat struct for the given fd. -PosixErrorOr<struct stat> Fstat(int fd); - -// Deletes the file or directory at path or returns an error. -PosixError Delete(absl::string_view path); - -// Changes the mode of a file or returns an error. -PosixError Chmod(absl::string_view path, int mode); - -// Create a special or ordinary file. -PosixError MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode, - dev_t dev); - -// Unlink the file. -PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, - int flags); - -// Truncates a file to the given length or returns an error. -PosixError Truncate(absl::string_view path, int length); - -// Returns true/false depending on whether or not the path is a directory or -// returns an error. -PosixErrorOr<bool> IsDirectory(absl::string_view path); - -// Makes a directory or returns an error. -PosixError Mkdir(absl::string_view path, int mode = 0755); - -// Removes a directory or returns an error. -PosixError Rmdir(absl::string_view path); - -// Attempts to set the contents of a file or returns an error. -PosixError SetContents(absl::string_view path, absl::string_view contents); - -// Creates a file with the given contents and mode or returns an error. -PosixError CreateWithContents(absl::string_view path, - absl::string_view contents, int mode = 0666); - -// Attempts to read the entire contents of the file into the provided string -// buffer or returns an error. -PosixError GetContents(absl::string_view path, std::string* output); - -// Attempts to read the entire contents of the file or returns an error. -PosixErrorOr<std::string> GetContents(absl::string_view path); - -// Attempts to read the entire contents of the provided fd into the provided -// string or returns an error. -PosixError GetContentsFD(int fd, std::string* output); - -// Attempts to read the entire contents of the provided fd or returns an error. -PosixErrorOr<std::string> GetContentsFD(int fd); - -// Executes the readlink(2) system call or returns an error. -PosixErrorOr<std::string> ReadLink(absl::string_view path); - -// WalkTree will walk a directory tree in a depth first search manner (if -// recursive). It will invoke a provided callback for each file and directory, -// the parent will always be invoked last making this appropriate for things -// such as deleting an entire directory tree. -// -// This method will return an error when it's unable to access the provided -// path, or when the path is not a directory. -PosixError WalkTree( - absl::string_view path, bool recursive, - const std::function<void(absl::string_view, const struct stat&)>& cb); - -// Returns the base filenames for all files under a given absolute path. If -// skipdots is true the returned vector will not contain "." or "..". This -// method does not walk the tree recursively it only returns the elements -// in that directory. -PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, - bool skipdots); - -// Attempt to recursively delete a directory or file. Returns an error and -// the number of undeleted directories and files. If either -// undeleted_dirs or undeleted_files is nullptr then it will not be used. -PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, - int* undeleted_files); - -// Recursively create the directory provided or return an error. -PosixError RecursivelyCreateDir(absl::string_view path); - -// Makes a path absolute with respect to an optional base. If no base is -// provided it will use the current working directory. -PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, - absl::string_view base); - -// Generates a relative path from the source directory to the destination -// (dest) file or directory. This uses ../ when necessary for destinations -// which are not nested within the source. Both source and dest are required -// to be absolute paths, and an empty string will be returned if they are not. -PosixErrorOr<std::string> GetRelativePath(absl::string_view source, - absl::string_view dest); - -// Returns the part of the path before the final "/", EXCEPT: -// * If there is a single leading "/" in the path, the result will be the -// leading "/". -// * If there is no "/" in the path, the result is the empty prefix of the -// input string. -absl::string_view Dirname(absl::string_view path); - -// Return the parts of the path, split on the final "/". If there is no -// "/" in the path, the first part of the output is empty and the second -// is the input. If the only "/" in the path is the first character, it is -// the first part of the output. -std::pair<absl::string_view, absl::string_view> SplitPath( - absl::string_view path); - -// Returns the part of the path after the final "/". If there is no -// "/" in the path, the result is the same as the input. -// Note that this function's behavior differs from the Unix basename -// command if path ends with "/". For such paths, this function returns the -// empty string. -absl::string_view Basename(absl::string_view path); - -// Collapse duplicate "/"s, resolve ".." and "." path elements, remove -// trailing "/". -// -// NOTE: This respects relative vs. absolute paths, but does not -// invoke any system calls (getcwd(2)) in order to resolve relative -// paths wrt actual working directory. That is, this is purely a -// string manipulation, completely independent of process state. -std::string CleanPath(absl::string_view path); - -// Returns the full path to the executable of the given pid or a PosixError. -PosixErrorOr<std::string> ProcessExePath(int pid); - -#ifdef __linux__ -// IsTmpfs returns true if the file at path is backed by tmpfs. -PosixErrorOr<bool> IsTmpfs(const std::string& path); -#endif // __linux__ - -// IsOverlayfs returns true if the file at path is backed by overlayfs. -PosixErrorOr<bool> IsOverlayfs(const std::string& path); - -PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2); - -namespace internal { -// Not part of the public API. -std::string JoinPathImpl(std::initializer_list<absl::string_view> paths); -} // namespace internal - -// Join multiple paths together. -// All paths will be treated as relative paths, regardless of whether or not -// they start with a leading '/'. That is, all paths will be concatenated -// together, with the appropriate path separator inserted in between. -// Arguments must be convertible to absl::string_view. -// -// Usage: -// std::string path = JoinPath("/foo", dirname, filename); -// std::string path = JoinPath(FLAGS_test_srcdir, filename); -// -// 0, 1, 2-path specializations exist to optimize common cases. -inline std::string JoinPath() { return std::string(); } -inline std::string JoinPath(absl::string_view path) { - return std::string(path.data(), path.size()); -} - -std::string JoinPath(absl::string_view path1, absl::string_view path2); -template <typename... T> -inline std::string JoinPath(absl::string_view path1, absl::string_view path2, - absl::string_view path3, const T&... args) { - return internal::JoinPathImpl({path1, path2, path3, args...}); -} -} // namespace testing -} // namespace gvisor -#endif // GVISOR_TEST_UTIL_FS_UTIL_H_ diff --git a/test/util/fs_util_test.cc b/test/util/fs_util_test.cc deleted file mode 100644 index 657b6a46e..000000000 --- a/test/util/fs_util_test.cc +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/fs_util.h" - -#include <errno.h> - -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(FsUtilTest, RecursivelyCreateDirManualDelete) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string base_path = - JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); - - ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); - ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); - - // Delete everything until we hit root and then stop, we want to try this - // without using RecursivelyDelete. - std::string cur_path = base_path; - while (cur_path != root.path()) { - ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(true)); - ASSERT_NO_ERRNO(Rmdir(cur_path)); - ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(false)); - auto dir = Dirname(cur_path); - cur_path = std::string(dir); - } -} - -TEST(FsUtilTest, RecursivelyCreateAndDeleteDir) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string base_path = - JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); - - ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); - ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); - - const std::string sub_path = JoinPath(root.path(), "a"); - ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr)); - ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false)); -} - -TEST(FsUtilTest, RecursivelyCreateAndDeletePartial) { - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const std::string base_path = - JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); - - ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); - ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); - - const std::string a = JoinPath(root.path(), "a"); - auto listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, true)); - ASSERT_THAT(listing, ::testing::Contains("b")); - ASSERT_EQ(listing.size(), 1); - - listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, false)); - ASSERT_THAT(listing, ::testing::Contains(".")); - ASSERT_THAT(listing, ::testing::Contains("..")); - ASSERT_THAT(listing, ::testing::Contains("b")); - ASSERT_EQ(listing.size(), 3); - - const std::string sub_path = JoinPath(root.path(), "/a/b/c/d/e/f"); - - ASSERT_NO_ERRNO( - CreateWithContents(JoinPath(Dirname(sub_path), "file"), "Hello World")); - std::string contents = ""; - ASSERT_NO_ERRNO(GetContents(JoinPath(Dirname(sub_path), "file"), &contents)); - ASSERT_EQ(contents, "Hello World"); - - ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr)); - ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false)); - - // The parent of the subpath (directory e) should still exist. - ASSERT_THAT(Exists(Dirname(sub_path)), IsPosixErrorOkAndHolds(true)); - - // The file we created along side f should also still exist. - ASSERT_THAT(Exists(JoinPath(Dirname(sub_path), "file")), - IsPosixErrorOkAndHolds(true)); -} -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/util/fuse_util.cc b/test/util/fuse_util.cc deleted file mode 100644 index 027f8386c..000000000 --- a/test/util/fuse_util.cc +++ /dev/null @@ -1,63 +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. - -#include "test/util/fuse_util.h" - -#include <sys/stat.h> -#include <sys/types.h> - -#include <string> - -namespace gvisor { -namespace testing { - -// Create a default FuseAttr struct with specified mode, inode, and size. -fuse_attr DefaultFuseAttr(mode_t mode, uint64_t inode, uint64_t size) { - const int time_sec = 1595436289; - const int time_nsec = 134150844; - return (struct fuse_attr){ - .ino = inode, - .size = size, - .blocks = 4, - .atime = time_sec, - .mtime = time_sec, - .ctime = time_sec, - .atimensec = time_nsec, - .mtimensec = time_nsec, - .ctimensec = time_nsec, - .mode = mode, - .nlink = 2, - .uid = 1234, - .gid = 4321, - .rdev = 12, - .blksize = 4096, - }; -} - -// Create response body with specified mode, nodeID, and size. -fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t node_id, uint64_t size) { - struct fuse_entry_out default_entry_out = { - .nodeid = node_id, - .generation = 0, - .entry_valid = 0, - .attr_valid = 0, - .entry_valid_nsec = 0, - .attr_valid_nsec = 0, - .attr = DefaultFuseAttr(mode, node_id, size), - }; - return default_entry_out; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/fuse_util.h b/test/util/fuse_util.h deleted file mode 100644 index 544fe1b38..000000000 --- a/test/util/fuse_util.h +++ /dev/null @@ -1,75 +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. - -#ifndef GVISOR_TEST_UTIL_FUSE_UTIL_H_ -#define GVISOR_TEST_UTIL_FUSE_UTIL_H_ - -#include <linux/fuse.h> -#include <sys/uio.h> - -#include <string> -#include <vector> - -namespace gvisor { -namespace testing { - -// The fundamental generation function with a single argument. If passed by -// std::string or std::vector<char>, it will call specialized versions as -// implemented below. -template <typename T> -std::vector<struct iovec> FuseGenerateIovecs(T &first) { - return {(struct iovec){.iov_base = &first, .iov_len = sizeof(first)}}; -} - -// If an argument is of type std::string, it must be used in read-only scenario. -// Because we are setting up iovec, which contains the original address of a -// data structure, we have to drop const qualification. Usually used with -// variable-length payload data. -template <typename T = std::string> -std::vector<struct iovec> FuseGenerateIovecs(std::string &first) { - // Pad one byte for null-terminate c-string. - return {(struct iovec){.iov_base = const_cast<char *>(first.c_str()), - .iov_len = first.size() + 1}}; -} - -// If an argument is of type std::vector<char>, it must be used in write-only -// scenario and the size of the variable must be greater than or equal to the -// size of the expected data. Usually used with variable-length payload data. -template <typename T = std::vector<char>> -std::vector<struct iovec> FuseGenerateIovecs(std::vector<char> &first) { - return {(struct iovec){.iov_base = first.data(), .iov_len = first.size()}}; -} - -// A helper function to set up an array of iovec struct for testing purpose. -// Use variadic class template to generalize different numbers and different -// types of FUSE structs. -template <typename T, typename... Types> -std::vector<struct iovec> FuseGenerateIovecs(T &first, Types &...args) { - auto first_iovec = FuseGenerateIovecs(first); - auto iovecs = FuseGenerateIovecs(args...); - first_iovec.insert(std::end(first_iovec), std::begin(iovecs), - std::end(iovecs)); - return first_iovec; -} - -// Create a fuse_attr filled with the specified mode and inode. -fuse_attr DefaultFuseAttr(mode_t mode, uint64_t inode, uint64_t size = 512); - -// Return a fuse_entry_out FUSE server response body. -fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t node_id, - uint64_t size = 512); - -} // namespace testing -} // namespace gvisor -#endif // GVISOR_TEST_UTIL_FUSE_UTIL_H_ diff --git a/test/util/logging.cc b/test/util/logging.cc deleted file mode 100644 index 5fadb076b..000000000 --- a/test/util/logging.cc +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/logging.h" - -#include <errno.h> -#include <stdint.h> -#include <stdlib.h> -#include <unistd.h> - -namespace gvisor { -namespace testing { - -namespace { - -// We implement this here instead of using test_util to avoid cyclic -// dependencies. -int Write(int fd, const char* buf, size_t size) { - size_t written = 0; - while (written < size) { - int res = write(fd, buf + written, size - written); - if (res < 0 && errno == EINTR) { - continue; - } else if (res <= 0) { - break; - } - - written += res; - } - return static_cast<int>(written); -} - -// Write 32-bit decimal number to fd. -int WriteNumber(int fd, uint32_t val) { - constexpr char kDigits[] = "0123456789"; - constexpr int kBase = 10; - - // 10 chars for 32-bit number in decimal, 1 char for the NUL-terminator. - constexpr int kBufferSize = 11; - char buf[kBufferSize]; - - // Convert the number to string. - char* s = buf + sizeof(buf) - 1; - size_t size = 0; - - *s = '\0'; - do { - s--; - size++; - - *s = kDigits[val % kBase]; - val /= kBase; - } while (val); - - return Write(fd, s, size); -} - -} // namespace - -void CheckFailure(const char* cond, size_t cond_size, const char* msg, - size_t msg_size, int errno_value) { - constexpr char kCheckFailure[] = "Check failed: "; - Write(2, kCheckFailure, sizeof(kCheckFailure) - 1); - Write(2, cond, cond_size); - - if (msg != nullptr) { - Write(2, ": ", 2); - Write(2, msg, msg_size); - } - - if (errno_value != 0) { - constexpr char kErrnoMessage[] = " (errno "; - Write(2, kErrnoMessage, sizeof(kErrnoMessage) - 1); - WriteNumber(2, errno_value); - Write(2, ")", 1); - } - - Write(2, "\n", 1); - - abort(); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/logging.h b/test/util/logging.h deleted file mode 100644 index 5c17f1233..000000000 --- a/test/util/logging.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_LOGGING_H_ -#define GVISOR_TEST_UTIL_LOGGING_H_ - -#include <stddef.h> - -namespace gvisor { -namespace testing { - -void CheckFailure(const char* cond, size_t cond_size, const char* msg, - size_t msg_size, int errno_value); - -// If cond is false, aborts the current process. -// -// This macro is async-signal-safe. -#define TEST_CHECK(cond) \ - do { \ - if (!(cond)) { \ - ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \ - 0, 0); \ - } \ - } while (0) - -// If cond is false, logs msg then aborts the current process. -// -// This macro is async-signal-safe. -#define TEST_CHECK_MSG(cond, msg) \ - do { \ - if (!(cond)) { \ - ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \ - sizeof(msg) - 1, 0); \ - } \ - } while (0) - -// If cond is false, logs errno, then aborts the current process. -// -// This macro is async-signal-safe. -#define TEST_PCHECK(cond) \ - do { \ - if (!(cond)) { \ - ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \ - 0, errno); \ - } \ - } while (0) - -// If cond is false, logs msg and errno, then aborts the current process. -// -// This macro is async-signal-safe. -#define TEST_PCHECK_MSG(cond, msg) \ - do { \ - if (!(cond)) { \ - ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \ - sizeof(msg) - 1, errno); \ - } \ - } while (0) - -// expr must return PosixErrorOr<T>. The current process is aborted if -// !PosixError<T>.ok(). -// -// This macro is async-signal-safe. -#define TEST_CHECK_NO_ERRNO(expr) \ - ({ \ - auto _expr_result = (expr); \ - if (!_expr_result.ok()) { \ - ::gvisor::testing::CheckFailure( \ - #expr, sizeof(#expr) - 1, nullptr, 0, \ - _expr_result.error().errno_value()); \ - } \ - }) - -// expr must return PosixErrorOr<T>. The current process is aborted if -// !PosixError<T>.ok(). Otherwise, PosixErrorOr<T> value is returned. -// -// This macro is async-signal-safe. -#define TEST_CHECK_NO_ERRNO_AND_VALUE(expr) \ - ({ \ - auto _expr_result = (expr); \ - if (!_expr_result.ok()) { \ - ::gvisor::testing::CheckFailure( \ - #expr, sizeof(#expr) - 1, nullptr, 0, \ - _expr_result.error().errno_value()); \ - } \ - std::move(_expr_result).ValueOrDie(); \ - }) - -// cond must be greater or equal than 0. Used to test result of syscalls. -// -// This macro is async-signal-safe. -#define TEST_CHECK_SUCCESS(cond) TEST_PCHECK((cond) >= 0) - -// cond must be -1 and errno must match errno_value. Used to test errors from -// syscalls. -// -// This macro is async-signal-safe. -#define TEST_CHECK_ERRNO(cond, errno_value) \ - do { \ - TEST_PCHECK((cond) == -1); \ - TEST_PCHECK_MSG(errno == (errno_value), #cond " expected " #errno_value); \ - } while (0) - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_LOGGING_H_ diff --git a/test/util/memory_util.h b/test/util/memory_util.h deleted file mode 100644 index e189b73e8..000000000 --- a/test/util/memory_util.h +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_MEMORY_UTIL_H_ -#define GVISOR_TEST_UTIL_MEMORY_UTIL_H_ - -#include <errno.h> -#include <stddef.h> -#include <stdint.h> -#include <sys/mman.h> - -#include "absl/strings/str_format.h" -#include "absl/strings/string_view.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// RAII type for mmap'ed memory. Only usable in tests due to use of a test-only -// macro that can't be named without invoking the presubmit's wrath. -class Mapping { - public: - // Constructs a mapping that owns nothing. - Mapping() = default; - - // Constructs a mapping that owns the mmapped memory [ptr, ptr+len). Most - // users should use Mmap or MmapAnon instead. - Mapping(void* ptr, size_t len) : ptr_(ptr), len_(len) {} - - Mapping(Mapping&& orig) : ptr_(orig.ptr_), len_(orig.len_) { orig.release(); } - - Mapping& operator=(Mapping&& orig) { - ptr_ = orig.ptr_; - len_ = orig.len_; - orig.release(); - return *this; - } - - Mapping(Mapping const&) = delete; - Mapping& operator=(Mapping const&) = delete; - - ~Mapping() { reset(); } - - void* ptr() const { return ptr_; } - size_t len() const { return len_; } - - // Returns a pointer to the end of the mapping. Useful for when the mapping - // is used as a thread stack. - void* endptr() const { return reinterpret_cast<void*>(addr() + len_); } - - // Returns the start of this mapping cast to uintptr_t for ease of pointer - // arithmetic. - uintptr_t addr() const { return reinterpret_cast<uintptr_t>(ptr_); } - - // Returns the end of this mapping cast to uintptr_t for ease of pointer - // arithmetic. - uintptr_t endaddr() const { return reinterpret_cast<uintptr_t>(endptr()); } - - // Returns this mapping as a StringPiece for ease of comparison. - // - // This function is named view in anticipation of the eventual replacement of - // StringPiece with std::string_view. - absl::string_view view() const { - return absl::string_view(static_cast<char const*>(ptr_), len_); - } - - // These are both named reset for consistency with standard smart pointers. - - void reset(void* ptr, size_t len) { - if (len_) { - TEST_PCHECK(munmap(ptr_, len_) == 0); - } - ptr_ = ptr; - len_ = len; - } - - void reset() { reset(nullptr, 0); } - - void release() { - ptr_ = nullptr; - len_ = 0; - } - - private: - void* ptr_ = nullptr; - size_t len_ = 0; -}; - -// Wrapper around mmap(2) that returns a Mapping. -inline PosixErrorOr<Mapping> Mmap(void* addr, size_t length, int prot, - int flags, int fd, off_t offset) { - void* ptr = mmap(addr, length, prot, flags, fd, offset); - if (ptr == MAP_FAILED) { - return PosixError( - errno, absl::StrFormat("mmap(%p, %d, %x, %x, %d, %d)", addr, length, - prot, flags, fd, offset)); - } - MaybeSave(); - return Mapping(ptr, length); -} - -// Convenience wrapper around Mmap for anonymous mappings. -inline PosixErrorOr<Mapping> MmapAnon(size_t length, int prot, int flags) { - return Mmap(nullptr, length, prot, flags | MAP_ANONYMOUS, -1, 0); -} - -// Wrapper for mremap that returns a PosixErrorOr<>, since the return type of -// void* isn't directly compatible with SyscallSucceeds. -inline PosixErrorOr<void*> Mremap(void* old_address, size_t old_size, - size_t new_size, int flags, - void* new_address) { - void* rv = mremap(old_address, old_size, new_size, flags, new_address); - if (rv == MAP_FAILED) { - return PosixError(errno, "mremap failed"); - } - return rv; -} - -// Returns true if the page containing addr is mapped. -inline bool IsMapped(uintptr_t addr) { - int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)), - kPageSize, MS_ASYNC); - if (rv == 0) { - return true; - } - TEST_PCHECK_MSG(errno == ENOMEM, "msync failed with unexpected errno"); - return false; -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_MEMORY_UTIL_H_ diff --git a/test/util/mount_util.h b/test/util/mount_util.h deleted file mode 100644 index 09e2281eb..000000000 --- a/test/util/mount_util.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_MOUNT_UTIL_H_ -#define GVISOR_TEST_UTIL_MOUNT_UTIL_H_ - -#include <errno.h> -#include <sys/mount.h> - -#include <functional> -#include <string> - -#include "gmock/gmock.h" -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Mount mounts the filesystem, and unmounts when the returned reference is -// destroyed. -inline PosixErrorOr<Cleanup> Mount(const std::string& source, - const std::string& target, - const std::string& fstype, - uint64_t mountflags, const std::string& data, - uint64_t umountflags) { - if (mount(source.c_str(), target.c_str(), fstype.c_str(), mountflags, - data.c_str()) == -1) { - return PosixError(errno, "mount failed"); - } - return Cleanup([target, umountflags]() { - EXPECT_THAT(umount2(target.c_str(), umountflags), SyscallSucceeds()); - }); -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_MOUNT_UTIL_H_ diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc deleted file mode 100644 index a6b0de24b..000000000 --- a/test/util/multiprocess_util.cc +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/multiprocess_util.h" - -#include <asm/unistd.h> -#include <errno.h> -#include <fcntl.h> -#include <signal.h> -#include <sys/prctl.h> -#include <unistd.h> - -#include "absl/strings/str_cat.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -// exec_fn wraps a variant of the exec family, e.g. execve or execveat. -PosixErrorOr<Cleanup> ForkAndExecHelper(const std::function<void()>& exec_fn, - const std::function<void()>& fn, - pid_t* child, int* execve_errno) { - int pfds[2]; - int ret = pipe2(pfds, O_CLOEXEC); - if (ret < 0) { - return PosixError(errno, "pipe failed"); - } - FileDescriptor rfd(pfds[0]); - FileDescriptor wfd(pfds[1]); - - int parent_stdout = dup(STDOUT_FILENO); - if (parent_stdout < 0) { - return PosixError(errno, "dup stdout"); - } - int parent_stderr = dup(STDERR_FILENO); - if (parent_stdout < 0) { - return PosixError(errno, "dup stderr"); - } - - pid_t pid = fork(); - if (pid < 0) { - return PosixError(errno, "fork failed"); - } else if (pid == 0) { - // Child. - rfd.reset(); - if (dup2(parent_stdout, STDOUT_FILENO) < 0) { - _exit(3); - } - if (dup2(parent_stderr, STDERR_FILENO) < 0) { - _exit(4); - } - close(parent_stdout); - close(parent_stderr); - - // Clean ourself up in case the parent doesn't. - if (prctl(PR_SET_PDEATHSIG, SIGKILL)) { - _exit(3); - } - - if (fn) { - fn(); - } - - // Call variant of exec function. - exec_fn(); - - int error = errno; - if (WriteFd(pfds[1], &error, sizeof(error)) != sizeof(error)) { - // We can't do much if the write fails, but we can at least exit with a - // different code. - _exit(2); - } - _exit(1); - } - - // Parent. - if (child) { - *child = pid; - } - - auto cleanup = Cleanup([pid] { - kill(pid, SIGKILL); - RetryEINTR(waitpid)(pid, nullptr, 0); - }); - - wfd.reset(); - - int read_errno; - ret = ReadFd(rfd.get(), &read_errno, sizeof(read_errno)); - if (ret == 0) { - // Other end of the pipe closed, execve must have succeeded. - read_errno = 0; - } else if (ret < 0) { - return PosixError(errno, "read pipe failed"); - } else if (ret != sizeof(read_errno)) { - return PosixError(EPIPE, absl::StrCat("pipe read wrong size ", ret)); - } - - if (execve_errno) { - *execve_errno = read_errno; - } - - return std::move(cleanup); -} - -} // namespace - -PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, - const ExecveArray& argv, - const ExecveArray& envv, - const std::function<void()>& fn, pid_t* child, - int* execve_errno) { - char* const* argv_data = argv.get(); - char* const* envv_data = envv.get(); - const std::function<void()> exec_fn = [=] { - execve(filename.c_str(), argv_data, envv_data); - }; - return ForkAndExecHelper(exec_fn, fn, child, execve_errno); -} - -PosixErrorOr<Cleanup> ForkAndExecveat(const int32_t dirfd, - const std::string& pathname, - const ExecveArray& argv, - const ExecveArray& envv, const int flags, - const std::function<void()>& fn, - pid_t* child, int* execve_errno) { - char* const* argv_data = argv.get(); - char* const* envv_data = envv.get(); - const std::function<void()> exec_fn = [=] { - syscall(__NR_execveat, dirfd, pathname.c_str(), argv_data, envv_data, - flags); - }; - return ForkAndExecHelper(exec_fn, fn, child, execve_errno); -} - -PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn) { - pid_t pid = fork(); - if (pid == 0) { - fn(); - TEST_CHECK_MSG(!::testing::Test::HasFailure(), - "EXPECT*/ASSERT* failed. These are not async-signal-safe " - "and must not be called from fn."); - _exit(0); - } - MaybeSave(); - if (pid < 0) { - return PosixError(errno, "fork failed"); - } - - int status; - if (waitpid(pid, &status, 0) < 0) { - return PosixError(errno, "waitpid failed"); - } - - return status; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/multiprocess_util.h b/test/util/multiprocess_util.h deleted file mode 100644 index 840fde4ee..000000000 --- a/test/util/multiprocess_util.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ -#define GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ - -#include <unistd.h> - -#include <algorithm> -#include <string> -#include <utility> -#include <vector> - -#include "absl/strings/string_view.h" -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Immutable holder for a dynamically-sized array of pointers to mutable char, -// terminated by a null pointer, as required for the argv and envp arguments to -// execve(2). -class ExecveArray { - public: - // Constructs an empty ExecveArray. - ExecveArray() = default; - - // Constructs an ExecveArray by copying strings from the given range. T must - // be a range over ranges of char. - template <typename T> - explicit ExecveArray(T const& strs) : ExecveArray(strs.begin(), strs.end()) {} - - // Constructs an ExecveArray by copying strings from [first, last). InputIt - // must be an input iterator over a range over char. - template <typename InputIt> - ExecveArray(InputIt first, InputIt last) { - std::vector<size_t> offsets; - auto output_it = std::back_inserter(str_); - for (InputIt it = first; it != last; ++it) { - offsets.push_back(str_.size()); - auto const& s = *it; - std::copy(s.begin(), s.end(), output_it); - str_.push_back('\0'); - } - ptrs_.reserve(offsets.size() + 1); - for (auto offset : offsets) { - ptrs_.push_back(str_.data() + offset); - } - ptrs_.push_back(nullptr); - } - - // Constructs an ExecveArray by copying strings from list. This overload must - // exist independently of the single-argument template constructor because - // std::initializer_list does not participate in template argument deduction - // (i.e. cannot be type-inferred in an invocation of the templated - // constructor). - /* implicit */ ExecveArray(std::initializer_list<absl::string_view> list) - : ExecveArray(list.begin(), list.end()) {} - - // Disable move construction and assignment since ptrs_ points into str_. - ExecveArray(ExecveArray&&) = delete; - ExecveArray& operator=(ExecveArray&&) = delete; - - char* const* get() const { return ptrs_.data(); } - size_t get_size() { return str_.size(); } - - private: - std::vector<char> str_; - std::vector<char*> ptrs_; -}; - -// Simplified version of SubProcess. Returns OK and a cleanup function to kill -// the child if it made it to execve. -// -// fn is run between fork and exec. If it needs to fail, it should exit the -// process. -// -// The child pid is returned via child, if provided. -// execve's error code is returned via execve_errno, if provided. -PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, - const ExecveArray& argv, - const ExecveArray& envv, - const std::function<void()>& fn, pid_t* child, - int* execve_errno); - -inline PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, - const ExecveArray& argv, - const ExecveArray& envv, pid_t* child, - int* execve_errno) { - return ForkAndExec( - filename, argv, envv, [] {}, child, execve_errno); -} - -// Equivalent to ForkAndExec, except using dirfd and flags with execveat. -PosixErrorOr<Cleanup> ForkAndExecveat(int32_t dirfd, - const std::string& pathname, - const ExecveArray& argv, - const ExecveArray& envv, int flags, - const std::function<void()>& fn, - pid_t* child, int* execve_errno); - -inline PosixErrorOr<Cleanup> ForkAndExecveat(int32_t dirfd, - const std::string& pathname, - const ExecveArray& argv, - const ExecveArray& envv, int flags, - pid_t* child, int* execve_errno) { - return ForkAndExecveat( - dirfd, pathname, argv, envv, flags, [] {}, child, execve_errno); -} - -// Calls fn in a forked subprocess and returns the exit status of the -// subprocess. -// -// fn must be async-signal-safe. Use of ASSERT/EXPECT functions is prohibited. -// Use TEST_CHECK variants instead. -PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ diff --git a/test/util/platform_util.cc b/test/util/platform_util.cc deleted file mode 100644 index c9200d381..000000000 --- a/test/util/platform_util.cc +++ /dev/null @@ -1,48 +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. - -#include "test/util/platform_util.h" - -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -PlatformSupport PlatformSupport32Bit() { - if (GvisorPlatform() == Platform::kPtrace || - GvisorPlatform() == Platform::kKVM) { - return PlatformSupport::NotSupported; - } else { - return PlatformSupport::Allowed; - } -} - -PlatformSupport PlatformSupportAlignmentCheck() { - return PlatformSupport::Allowed; -} - -PlatformSupport PlatformSupportMultiProcess() { - return PlatformSupport::Allowed; -} - -PlatformSupport PlatformSupportInt3() { - if (GvisorPlatform() == Platform::kKVM) { - return PlatformSupport::NotSupported; - } else { - return PlatformSupport::Allowed; - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/platform_util.h b/test/util/platform_util.h deleted file mode 100644 index 28cc92371..000000000 --- a/test/util/platform_util.h +++ /dev/null @@ -1,56 +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. - -#ifndef GVISOR_TEST_UTIL_PLATFORM_UTIL_H_ -#define GVISOR_TEST_UTIL_PLATFORM_UTIL_H_ - -namespace gvisor { -namespace testing { - -// PlatformSupport is a generic enumeration of classes of support. -// -// It is up to the individual functions and callers to agree on the precise -// definition for each case. The document here generally refers to 32-bit -// as an example. Many cases will use only NotSupported and Allowed. -enum class PlatformSupport { - // The feature is not supported on the current platform. - // - // In the case of 32-bit, this means that calls will generally be interpreted - // as 64-bit calls, and there is no support for 32-bit binaries, long calls, - // etc. This usually means that the underlying implementation just pretends - // that 32-bit doesn't exist. - NotSupported, - - // Calls will be ignored by the kernel with a fixed error. - Ignored, - - // Calls will result in a SIGSEGV or similar fault. - Segfault, - - // The feature is supported as expected. - // - // In the case of 32-bit, this means that the system call or far call will be - // handled properly. - Allowed, -}; - -PlatformSupport PlatformSupport32Bit(); -PlatformSupport PlatformSupportAlignmentCheck(); -PlatformSupport PlatformSupportMultiProcess(); -PlatformSupport PlatformSupportInt3(); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_PLATFORM_UTL_H_ diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc deleted file mode 100644 index 8522e4c81..000000000 --- a/test/util/posix_error.cc +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/posix_error.h" - -#include <cassert> -#include <cerrno> -#include <cstring> -#include <string> - -#include "absl/strings/str_cat.h" - -namespace gvisor { -namespace testing { - -std::string PosixError::ToString() const { - if (ok()) { - return "No Error"; - } - - std::string ret; - - char strerrno_buf[1024] = {}; - - auto res = strerror_r(errno_, strerrno_buf, sizeof(strerrno_buf)); - -// The GNU version of strerror_r always returns a non-null char* pointing to a -// buffer containing the stringified errno; the XSI version returns a positive -// errno which indicates the result of writing the stringified errno into the -// supplied buffer. The gymnastics below are needed to support both. -#ifndef _GNU_SOURCE - if (res != 0) { - ret = absl::StrCat("PosixError(errno=", errno_, " strerror_r FAILED(", ret, - "))"); - } else { - ret = absl::StrCat("PosixError(errno=", errno_, " ", strerrno_buf, ")"); - } -#else - ret = absl::StrCat("PosixError(errno=", errno_, " ", res, ")"); -#endif - - if (strnlen(msg_, sizeof(msg_)) > 0) { - ret.append(" "); - ret.append(msg_); - } - - return ret; -} - -::std::ostream& operator<<(::std::ostream& os, const PosixError& e) { - os << e.ToString(); - return os; -} - -void PosixErrorIsMatcherCommonImpl::DescribeTo(std::ostream* os) const { - *os << "has an errno value that "; - code_matcher_.DescribeTo(os); - *os << ", and has an error message that "; - message_matcher_.DescribeTo(os); -} - -void PosixErrorIsMatcherCommonImpl::DescribeNegationTo(std::ostream* os) const { - *os << "has an errno value that "; - code_matcher_.DescribeNegationTo(os); - *os << ", or has an error message that "; - message_matcher_.DescribeNegationTo(os); -} - -bool PosixErrorIsMatcherCommonImpl::MatchAndExplain( - const PosixError& error, - ::testing::MatchResultListener* result_listener) const { - ::testing::StringMatchResultListener inner_listener; - - inner_listener.Clear(); - if (!code_matcher_.MatchAndExplain(error.errno_value(), &inner_listener)) { - return false; - } - - if (!message_matcher_.Matches(error.message())) { - return false; - } - - return true; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/posix_error.h b/test/util/posix_error.h deleted file mode 100644 index 27557ad44..000000000 --- a/test/util/posix_error.h +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_POSIX_ERROR_H_ -#define GVISOR_TEST_UTIL_POSIX_ERROR_H_ - -#include <string> - -#include "gmock/gmock.h" -#include "absl/base/attributes.h" -#include "absl/strings/string_view.h" -#include "absl/types/variant.h" -#include "test/util/logging.h" - -namespace gvisor { -namespace testing { - -// PosixError must be async-signal-safe. -class ABSL_MUST_USE_RESULT PosixError { - public: - PosixError() {} - - explicit PosixError(int errno_value) : errno_(errno_value) {} - - PosixError(int errno_value, std::string_view msg) : errno_(errno_value) { - // Check that `msg` will fit, leaving room for '\0' at the end. - TEST_CHECK(msg.size() < sizeof(msg_)); - msg.copy(msg_, msg.size()); - } - - PosixError(PosixError&& other) = default; - PosixError& operator=(PosixError&& other) = default; - PosixError(const PosixError&) = default; - PosixError& operator=(const PosixError&) = default; - - bool ok() const { return errno_ == 0; } - - // Returns a reference to *this to make matchers compatible with - // PosixErrorOr. - const PosixError& error() const { return *this; } - - int errno_value() const { return errno_; } - const char* message() const { return msg_; } - - // ToString produces a full string representation of this posix error - // including the printable representation of the errno and the error message. - std::string ToString() const; - - // Ignores any errors. This method does nothing except potentially suppress - // complaints from any tools that are checking that errors are not dropped on - // the floor. - void IgnoreError() const {} - - private: - int errno_ = 0; - char msg_[1024] = {}; -}; - -template <typename T> -class ABSL_MUST_USE_RESULT PosixErrorOr { - public: - // A PosixErrorOr will check fail if it is constructed with NoError(). - PosixErrorOr(const PosixError& error); - PosixErrorOr(const T& value); - PosixErrorOr(T&& value); - - PosixErrorOr(PosixErrorOr&& other) = default; - PosixErrorOr& operator=(PosixErrorOr&& other) = default; - PosixErrorOr(const PosixErrorOr&) = default; - PosixErrorOr& operator=(const PosixErrorOr&) = default; - - // Conversion copy/move constructor, T must be convertible from U. - template <typename U> - friend class PosixErrorOr; - - template <typename U> - PosixErrorOr(PosixErrorOr<U> other); - - template <typename U> - PosixErrorOr& operator=(PosixErrorOr<U> other); - - // Returns true if this PosixErrorOr contains some T. - bool ok() const; - - // Return a copy of the contained PosixError or NoError(). - PosixError error() const; - - // Returns a reference to our current value, or CHECK-fails if !this->ok(). - const T& ValueOrDie() const&; - T& ValueOrDie() &; - const T&& ValueOrDie() const&&; - T&& ValueOrDie() &&; - - // Ignores any errors. This method does nothing except potentially suppress - // complaints from any tools that are checking that errors are not dropped on - // the floor. - void IgnoreError() const {} - - private: - absl::variant<T, PosixError> value_; - - friend class PosixErrorIsMatcherCommonImpl; -}; - -template <typename T> -PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) { - TEST_CHECK_MSG( - !error.ok(), - "Constructing PosixErrorOr with NoError, eg. errno 0 is not allowed."); -} - -template <typename T> -PosixErrorOr<T>::PosixErrorOr(const T& value) : value_(value) {} - -template <typename T> -PosixErrorOr<T>::PosixErrorOr(T&& value) : value_(std::move(value)) {} - -// Conversion copy/move constructor, T must be convertible from U. -template <typename T> -template <typename U> -inline PosixErrorOr<T>::PosixErrorOr(PosixErrorOr<U> other) { - if (absl::holds_alternative<U>(other.value_)) { - // T is convertible from U. - value_ = absl::get<U>(std::move(other.value_)); - } else if (absl::holds_alternative<PosixError>(other.value_)) { - value_ = absl::get<PosixError>(std::move(other.value_)); - } else { - TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value"); - } -} - -template <typename T> -template <typename U> -inline PosixErrorOr<T>& PosixErrorOr<T>::operator=(PosixErrorOr<U> other) { - if (absl::holds_alternative<U>(other.value_)) { - // T is convertible from U. - value_ = absl::get<U>(std::move(other.value_)); - } else if (absl::holds_alternative<PosixError>(other.value_)) { - value_ = absl::get<PosixError>(std::move(other.value_)); - } else { - TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value"); - } - return *this; -} - -template <typename T> -PosixError PosixErrorOr<T>::error() const { - if (!absl::holds_alternative<PosixError>(value_)) { - return PosixError(); - } - return absl::get<PosixError>(value_); -} - -template <typename T> -bool PosixErrorOr<T>::ok() const { - return absl::holds_alternative<T>(value_); -} - -template <typename T> -const T& PosixErrorOr<T>::ValueOrDie() const& { - TEST_CHECK(absl::holds_alternative<T>(value_)); - return absl::get<T>(value_); -} - -template <typename T> -T& PosixErrorOr<T>::ValueOrDie() & { - TEST_CHECK(absl::holds_alternative<T>(value_)); - return absl::get<T>(value_); -} - -template <typename T> -const T&& PosixErrorOr<T>::ValueOrDie() const&& { - TEST_CHECK(absl::holds_alternative<T>(value_)); - return std::move(absl::get<T>(value_)); -} - -template <typename T> -T&& PosixErrorOr<T>::ValueOrDie() && { - TEST_CHECK(absl::holds_alternative<T>(value_)); - return std::move(absl::get<T>(value_)); -} - -extern ::std::ostream& operator<<(::std::ostream& os, const PosixError& e); - -template <typename T> -::std::ostream& operator<<(::std::ostream& os, const PosixErrorOr<T>& e) { - os << e.error(); - return os; -} - -// NoError is a PosixError that represents a successful state, i.e. No Error. -inline PosixError NoError() { return PosixError(); } - -// Monomorphic implementation of matcher IsPosixErrorOk() for a given type T. -// T can be PosixError, PosixErrorOr<>, or a reference to either of them. -template <typename T> -class MonoPosixErrorIsOkMatcherImpl : public ::testing::MatcherInterface<T> { - public: - void DescribeTo(std::ostream* os) const override { *os << "is OK"; } - void DescribeNegationTo(std::ostream* os) const override { - *os << "is not OK"; - } - bool MatchAndExplain(T actual_value, - ::testing::MatchResultListener*) const override { - return actual_value.ok(); - } -}; - -// Implements IsPosixErrorOkMatcher() as a polymorphic matcher. -class IsPosixErrorOkMatcher { - public: - template <typename T> - operator ::testing::Matcher<T>() const { // NOLINT - return MakeMatcher(new MonoPosixErrorIsOkMatcherImpl<T>()); - } -}; - -// Monomorphic implementation of a matcher for a PosixErrorOr. -template <typename PosixErrorOrType> -class IsPosixErrorOkAndHoldsMatcherImpl - : public ::testing::MatcherInterface<PosixErrorOrType> { - public: - using ValueType = typename std::remove_reference<decltype( - std::declval<PosixErrorOrType>().ValueOrDie())>::type; - - template <typename InnerMatcher> - explicit IsPosixErrorOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher) - : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>( - std::forward<InnerMatcher>(inner_matcher))) {} - - void DescribeTo(std::ostream* os) const override { - *os << "is OK and has a value that "; - inner_matcher_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "isn't OK or has a value that "; - inner_matcher_.DescribeNegationTo(os); - } - - bool MatchAndExplain( - PosixErrorOrType actual_value, - ::testing::MatchResultListener* listener) const override { - // We can't extract the value if it doesn't contain one. - if (!actual_value.ok()) { - return false; - } - - ::testing::StringMatchResultListener inner_listener; - const bool matches = inner_matcher_.MatchAndExplain( - actual_value.ValueOrDie(), &inner_listener); - const std::string inner_explanation = inner_listener.str(); - *listener << "has a value " - << ::testing::PrintToString(actual_value.ValueOrDie()); - - if (!inner_explanation.empty()) { - *listener << " " << inner_explanation; - } - return matches; - } - - private: - const ::testing::Matcher<const ValueType&> inner_matcher_; -}; - -// Implements IsOkAndHolds() as a polymorphic matcher. -template <typename InnerMatcher> -class IsPosixErrorOkAndHoldsMatcher { - public: - explicit IsPosixErrorOkAndHoldsMatcher(InnerMatcher inner_matcher) - : inner_matcher_(std::move(inner_matcher)) {} - - // Converts this polymorphic matcher to a monomorphic one of the given type. - // PosixErrorOrType can be either PosixErrorOr<T> or a reference to - // PosixErrorOr<T>. - template <typename PosixErrorOrType> - operator ::testing::Matcher<PosixErrorOrType>() const { // NOLINT - return ::testing::MakeMatcher( - new IsPosixErrorOkAndHoldsMatcherImpl<PosixErrorOrType>( - inner_matcher_)); - } - - private: - const InnerMatcher inner_matcher_; -}; - -// PosixErrorIs() is a polymorphic matcher. This class is the common -// implementation of it shared by all types T where PosixErrorIs() can be -// used as a Matcher<T>. -class PosixErrorIsMatcherCommonImpl { - public: - PosixErrorIsMatcherCommonImpl( - ::testing::Matcher<int> code_matcher, - ::testing::Matcher<const std::string&> message_matcher) - : code_matcher_(std::move(code_matcher)), - message_matcher_(std::move(message_matcher)) {} - - void DescribeTo(std::ostream* os) const; - - void DescribeNegationTo(std::ostream* os) const; - - bool MatchAndExplain(const PosixError& error, - ::testing::MatchResultListener* result_listener) const; - - template <typename T> - bool MatchAndExplain(const PosixErrorOr<T>& error_or, - ::testing::MatchResultListener* result_listener) const { - if (error_or.ok()) { - *result_listener << "has a value " - << ::testing::PrintToString(error_or.ValueOrDie()); - return false; - } - - return MatchAndExplain(error_or.error(), result_listener); - } - - private: - const ::testing::Matcher<int> code_matcher_; - const ::testing::Matcher<const std::string&> message_matcher_; -}; - -// Monomorphic implementation of matcher PosixErrorIs() for a given type -// T. T can be PosixError, PosixErrorOr<>, or a reference to either of them. -template <typename T> -class MonoPosixErrorIsMatcherImpl : public ::testing::MatcherInterface<T> { - public: - explicit MonoPosixErrorIsMatcherImpl( - PosixErrorIsMatcherCommonImpl common_impl) - : common_impl_(std::move(common_impl)) {} - - void DescribeTo(std::ostream* os) const override { - common_impl_.DescribeTo(os); - } - - void DescribeNegationTo(std::ostream* os) const override { - common_impl_.DescribeNegationTo(os); - } - - bool MatchAndExplain( - T actual_value, - ::testing::MatchResultListener* result_listener) const override { - return common_impl_.MatchAndExplain(actual_value, result_listener); - } - - private: - PosixErrorIsMatcherCommonImpl common_impl_; -}; - -inline ::testing::Matcher<int> ToErrorCodeMatcher( - const ::testing::Matcher<int>& m) { - return m; -} - -// Implements PosixErrorIs() as a polymorphic matcher. -class PosixErrorIsMatcher { - public: - template <typename ErrorCodeMatcher> - PosixErrorIsMatcher(ErrorCodeMatcher&& code_matcher, - ::testing::Matcher<const std::string&> message_matcher) - : common_impl_( - ToErrorCodeMatcher(std::forward<ErrorCodeMatcher>(code_matcher)), - std::move(message_matcher)) {} - - // Converts this polymorphic matcher to a monomorphic matcher of the - // given type. T can be StatusOr<>, Status, or a reference to - // either of them. - template <typename T> - operator ::testing::Matcher<T>() const { // NOLINT - return MakeMatcher(new MonoPosixErrorIsMatcherImpl<T>(common_impl_)); - } - - private: - const PosixErrorIsMatcherCommonImpl common_impl_; -}; - -// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose -// whose error code matches code_matcher, and whose error message matches -// message_matcher. -template <typename ErrorCodeMatcher> -PosixErrorIsMatcher PosixErrorIs( - ErrorCodeMatcher&& code_matcher, - ::testing::Matcher<const std::string&> message_matcher) { - return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher), - std::move(message_matcher)); -} - -// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and -// value matches the inner matcher. -template <typename InnerMatcher> -IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type> -IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) { - return IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>( - std::forward<InnerMatcher>(inner_matcher)); -} - -// Internal helper for concatenating macro values. -#define POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) x##y -#define POSIX_ERROR_IMPL_CONCAT_(x, y) POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) - -#define POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(posixerroror, lhs, rexpr) \ - auto posixerroror = (rexpr); \ - if (!posixerroror.ok()) { \ - return (posixerroror.error()); \ - } \ - lhs = std::move(posixerroror).ValueOrDie() - -#define EXPECT_NO_ERRNO(expression) \ - EXPECT_THAT(expression, IsPosixErrorOkMatcher()) -#define ASSERT_NO_ERRNO(expression) \ - ASSERT_THAT(expression, IsPosixErrorOkMatcher()) - -#define ASSIGN_OR_RETURN_ERRNO(lhs, rexpr) \ - POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_( \ - POSIX_ERROR_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr) - -#define RETURN_IF_ERRNO(s) \ - do { \ - if (!s.ok()) { \ - return s; \ - } \ - } while (false); - -#define ASSERT_NO_ERRNO_AND_VALUE(expr) \ - ({ \ - auto _expr_result = (expr); \ - ASSERT_NO_ERRNO(_expr_result); \ - std::move(_expr_result).ValueOrDie(); \ - }) - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_POSIX_ERROR_H_ diff --git a/test/util/posix_error_test.cc b/test/util/posix_error_test.cc deleted file mode 100644 index bf9465abb..000000000 --- a/test/util/posix_error_test.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/posix_error.h" - -#include <errno.h> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace gvisor { -namespace testing { - -namespace { - -TEST(PosixErrorTest, PosixError) { - auto err = PosixError(EAGAIN); - EXPECT_THAT(err, PosixErrorIs(EAGAIN, "")); -} - -TEST(PosixErrorTest, PosixErrorOrPosixError) { - auto err = PosixErrorOr<std::nullptr_t>(PosixError(EAGAIN)); - EXPECT_THAT(err, PosixErrorIs(EAGAIN, "")); -} - -TEST(PosixErrorTest, PosixErrorOrNullptr) { - auto err = PosixErrorOr<std::nullptr_t>(nullptr); - EXPECT_TRUE(err.ok()); - EXPECT_NO_ERRNO(err); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc deleted file mode 100644 index 34d636ba9..000000000 --- a/test/util/proc_util.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/proc_util.h" - -#include <algorithm> -#include <iostream> -#include <vector> - -#include "absl/strings/ascii.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "test/util/fs_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// Parses a single line from /proc/<xxx>/maps. -PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line) { - ProcMapsEntry map_entry = {}; - - // Limit splitting to 6 parts so that if there is a file path and it contains - // spaces, the file path is not split. - std::vector<std::string> parts = - absl::StrSplit(line, absl::MaxSplits(' ', 5), absl::SkipEmpty()); - - // parts.size() should be 6 if there is a file name specified, and 5 - // otherwise. - if (parts.size() < 5) { - return PosixError(EINVAL, absl::StrCat("Invalid line: ", line)); - } - - // Address range in the form X-X where X are hex values without leading 0x. - std::vector<std::string> addresses = absl::StrSplit(parts[0], '-'); - if (addresses.size() != 2) { - return PosixError(EINVAL, - absl::StrCat("Invalid address range: ", parts[0])); - } - ASSIGN_OR_RETURN_ERRNO(map_entry.start, AtoiBase(addresses[0], 16)); - ASSIGN_OR_RETURN_ERRNO(map_entry.end, AtoiBase(addresses[1], 16)); - - // Permissions are four bytes of the form rwxp or - if permission not set. - if (parts[1].size() != 4) { - return PosixError(EINVAL, - absl::StrCat("Invalid permission field: ", parts[1])); - } - - map_entry.readable = parts[1][0] == 'r'; - map_entry.writable = parts[1][1] == 'w'; - map_entry.executable = parts[1][2] == 'x'; - map_entry.priv = parts[1][3] == 'p'; - - ASSIGN_OR_RETURN_ERRNO(map_entry.offset, AtoiBase(parts[2], 16)); - - std::vector<std::string> device = absl::StrSplit(parts[3], ':'); - if (device.size() != 2) { - return PosixError(EINVAL, absl::StrCat("Invalid device: ", parts[3])); - } - ASSIGN_OR_RETURN_ERRNO(map_entry.major, AtoiBase(device[0], 16)); - ASSIGN_OR_RETURN_ERRNO(map_entry.minor, AtoiBase(device[1], 16)); - - ASSIGN_OR_RETURN_ERRNO(map_entry.inode, Atoi<int64_t>(parts[4])); - if (parts.size() == 6) { - // A filename is present. However, absl::StrSplit retained the whitespace - // between the inode number and the filename. - map_entry.filename = - std::string(absl::StripLeadingAsciiWhitespace(parts[5])); - } - - return map_entry; -} - -PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps( - absl::string_view contents) { - std::vector<ProcMapsEntry> entries; - auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); - for (const auto& l : lines) { - std::cout << "line: " << l << std::endl; - ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcMapsLine(l)); - entries.push_back(entry); - } - return entries; -} - -PosixErrorOr<bool> IsVsyscallEnabled() { - ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps")); - ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents)); - return std::any_of(maps.begin(), maps.end(), [](const ProcMapsEntry& e) { - return e.filename == "[vsyscall]"; - }); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/proc_util.h b/test/util/proc_util.h deleted file mode 100644 index af209a51e..000000000 --- a/test/util/proc_util.h +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_PROC_UTIL_H_ -#define GVISOR_TEST_UTIL_PROC_UTIL_H_ - -#include <ostream> -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// ProcMapsEntry contains the data from a single line in /proc/<xxx>/maps. -struct ProcMapsEntry { - uint64_t start; - uint64_t end; - bool readable; - bool writable; - bool executable; - bool priv; - uint64_t offset; - int major; - int minor; - int64_t inode; - std::string filename; -}; - -// Parses a ProcMaps line or returns an error. -PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line); -PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps( - absl::string_view contents); - -// Returns true if vsyscall (emmulation or not) is enabled. -PosixErrorOr<bool> IsVsyscallEnabled(); - -// Printer for ProcMapsEntry. -inline std::ostream& operator<<(std::ostream& os, const ProcMapsEntry& entry) { - std::string str = - absl::StrCat(absl::Hex(entry.start, absl::PadSpec::kZeroPad8), "-", - absl::Hex(entry.end, absl::PadSpec::kZeroPad8), " "); - - absl::StrAppend(&str, entry.readable ? "r" : "-"); - absl::StrAppend(&str, entry.writable ? "w" : "-"); - absl::StrAppend(&str, entry.executable ? "x" : "-"); - absl::StrAppend(&str, entry.priv ? "p" : "s"); - - absl::StrAppend(&str, " ", absl::Hex(entry.offset, absl::PadSpec::kZeroPad8), - " ", absl::Hex(entry.major, absl::PadSpec::kZeroPad2), ":", - absl::Hex(entry.minor, absl::PadSpec::kZeroPad2), " ", - entry.inode); - if (absl::string_view(entry.filename) != "") { - // Pad to column 74 - int pad = 73 - str.length(); - if (pad > 0) { - absl::StrAppend(&str, std::string(pad, ' ')); - } - absl::StrAppend(&str, entry.filename); - } - os << str; - return os; -} - -// Printer for std::vector<ProcMapsEntry>. -inline std::ostream& operator<<(std::ostream& os, - const std::vector<ProcMapsEntry>& vec) { - for (unsigned int i = 0; i < vec.size(); i++) { - os << vec[i]; - if (i != vec.size() - 1) { - os << "\n"; - } - } - return os; -} - -// GMock printer for std::vector<ProcMapsEntry>. -inline void PrintTo(const std::vector<ProcMapsEntry>& vec, std::ostream* os) { - *os << vec; -} - -// Checks that /proc/pid/maps contains all of the passed mappings. -// -// The major, minor, and inode fields are ignored. -MATCHER_P(ContainsMappings, mappings, - "contains mappings:\n" + ::testing::PrintToString(mappings)) { - auto contents_or = GetContents(absl::StrCat("/proc/", arg, "/maps")); - if (!contents_or.ok()) { - *result_listener << "Unable to read mappings: " - << contents_or.error().ToString(); - return false; - } - - auto maps_or = ParseProcMaps(contents_or.ValueOrDie()); - if (!maps_or.ok()) { - *result_listener << "Unable to parse mappings: " - << maps_or.error().ToString(); - return false; - } - - auto maps = std::move(maps_or).ValueOrDie(); - - // Does maps contain all elements in mappings? The comparator ignores - // the major, minor, and inode fields. - bool all_present = true; - std::for_each(mappings.begin(), mappings.end(), [&](const ProcMapsEntry& e1) { - auto it = - std::find_if(maps.begin(), maps.end(), [&e1](const ProcMapsEntry& e2) { - return e1.start == e2.start && e1.end == e2.end && - e1.readable == e2.readable && e1.writable == e2.writable && - e1.executable == e2.executable && e1.priv == e2.priv && - e1.offset == e2.offset && e1.filename == e2.filename; - }); - if (it == maps.end()) { - // It wasn't found. - if (all_present) { - // We will output the message once and then a line for each mapping - // that wasn't found. - all_present = false; - *result_listener << "Got mappings:\n" - << maps << "\nThat were missing:\n"; - } - *result_listener << e1 << "\n"; - } - }); - - return all_present; -} - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_PROC_UTIL_H_ diff --git a/test/util/proc_util_test.cc b/test/util/proc_util_test.cc deleted file mode 100644 index 71dd2355e..000000000 --- a/test/util/proc_util_test.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/proc_util.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/util/test_util.h" - -using ::testing::IsEmpty; - -namespace gvisor { -namespace testing { - -namespace { - -TEST(ParseProcMapsLineTest, WithoutFilename) { - auto entry = ASSERT_NO_ERRNO_AND_VALUE( - ParseProcMapsLine("2ab4f00b7000-2ab4f00b9000 r-xp 00000000 00:00 0 ")); - EXPECT_EQ(entry.start, 0x2ab4f00b7000); - EXPECT_EQ(entry.end, 0x2ab4f00b9000); - EXPECT_TRUE(entry.readable); - EXPECT_FALSE(entry.writable); - EXPECT_TRUE(entry.executable); - EXPECT_TRUE(entry.priv); - EXPECT_EQ(entry.offset, 0); - EXPECT_EQ(entry.major, 0); - EXPECT_EQ(entry.minor, 0); - EXPECT_EQ(entry.inode, 0); - EXPECT_THAT(entry.filename, IsEmpty()); -} - -TEST(ParseProcMapsLineTest, WithFilename) { - auto entry = ASSERT_NO_ERRNO_AND_VALUE( - ParseProcMapsLine("00407000-00408000 rw-p 00006000 00:0e 10 " - " /bin/cat")); - EXPECT_EQ(entry.start, 0x407000); - EXPECT_EQ(entry.end, 0x408000); - EXPECT_TRUE(entry.readable); - EXPECT_TRUE(entry.writable); - EXPECT_FALSE(entry.executable); - EXPECT_TRUE(entry.priv); - EXPECT_EQ(entry.offset, 0x6000); - EXPECT_EQ(entry.major, 0); - EXPECT_EQ(entry.minor, 0x0e); - EXPECT_EQ(entry.inode, 10); - EXPECT_EQ(entry.filename, "/bin/cat"); -} - -TEST(ParseProcMapsLineTest, WithFilenameContainingSpaces) { - auto entry = ASSERT_NO_ERRNO_AND_VALUE( - ParseProcMapsLine("7f26b3b12000-7f26b3b13000 rw-s 00000000 00:05 1432484 " - " /dev/zero (deleted)")); - EXPECT_EQ(entry.start, 0x7f26b3b12000); - EXPECT_EQ(entry.end, 0x7f26b3b13000); - EXPECT_TRUE(entry.readable); - EXPECT_TRUE(entry.writable); - EXPECT_FALSE(entry.executable); - EXPECT_FALSE(entry.priv); - EXPECT_EQ(entry.offset, 0); - EXPECT_EQ(entry.major, 0); - EXPECT_EQ(entry.minor, 0x05); - EXPECT_EQ(entry.inode, 1432484); - EXPECT_EQ(entry.filename, "/dev/zero (deleted)"); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/util/pty_util.cc b/test/util/pty_util.cc deleted file mode 100644 index 351f4730c..000000000 --- a/test/util/pty_util.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/pty_util.h" - -#include <sys/ioctl.h> -#include <termios.h> - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<FileDescriptor> OpenReplica(const FileDescriptor& master) { - return OpenReplica(master, O_NONBLOCK | O_RDWR | O_NOCTTY); -} - -PosixErrorOr<FileDescriptor> OpenReplica(const FileDescriptor& master, - int flags) { - PosixErrorOr<int> n = ReplicaID(master); - if (!n.ok()) { - return PosixErrorOr<FileDescriptor>(n.error()); - } - return Open(absl::StrCat("/dev/pts/", n.ValueOrDie()), flags); -} - -PosixErrorOr<int> ReplicaID(const FileDescriptor& master) { - // Get pty index. - int n; - int ret = ioctl(master.get(), TIOCGPTN, &n); - if (ret < 0) { - return PosixError(errno, "ioctl(TIOCGPTN) failed"); - } - - // Unlock pts. - int unlock = 0; - ret = ioctl(master.get(), TIOCSPTLCK, &unlock); - if (ret < 0) { - return PosixError(errno, "ioctl(TIOSPTLCK) failed"); - } - - return n; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/pty_util.h b/test/util/pty_util.h deleted file mode 100644 index 0cca2182c..000000000 --- a/test/util/pty_util.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_PTY_UTIL_H_ -#define GVISOR_TEST_UTIL_PTY_UTIL_H_ - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Opens the replica end of the passed master as R/W and nonblocking. It does -// not set the replica as the controlling TTY. -PosixErrorOr<FileDescriptor> OpenReplica(const FileDescriptor& master); - -// Identical to the above OpenReplica, but flags are all specified by the -// caller. -PosixErrorOr<FileDescriptor> OpenReplica(const FileDescriptor& master, - int flags); - -// Get the number of the replica end of the master. -PosixErrorOr<int> ReplicaID(const FileDescriptor& master); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_PTY_UTIL_H_ diff --git a/test/util/rlimit_util.cc b/test/util/rlimit_util.cc deleted file mode 100644 index d7bfc1606..000000000 --- a/test/util/rlimit_util.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/rlimit_util.h" - -#include <sys/resource.h> - -#include <cerrno> - -#include "test/util/cleanup.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval) { - struct rlimit old_rlim; - if (getrlimit(resource, &old_rlim) != 0) { - return PosixError(errno, "getrlimit failed"); - } - struct rlimit new_rlim = old_rlim; - new_rlim.rlim_cur = newval; - if (setrlimit(resource, &new_rlim) != 0) { - return PosixError(errno, "setrlimit failed"); - } - return Cleanup([resource, old_rlim] { - TEST_PCHECK(setrlimit(resource, &old_rlim) == 0); - }); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/rlimit_util.h b/test/util/rlimit_util.h deleted file mode 100644 index 873252a32..000000000 --- a/test/util/rlimit_util.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_RLIMIT_UTIL_H_ -#define GVISOR_TEST_UTIL_RLIMIT_UTIL_H_ - -#include <sys/resource.h> -#include <sys/time.h> - -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval); - -} // namespace testing -} // namespace gvisor -#endif // GVISOR_TEST_UTIL_RLIMIT_UTIL_H_ diff --git a/test/util/save_util.cc b/test/util/save_util.cc deleted file mode 100644 index 59d47e06e..000000000 --- a/test/util/save_util.cc +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/save_util.h" - -#include <stddef.h> -#include <stdlib.h> -#include <unistd.h> - -#include <atomic> -#include <cerrno> - -#include "absl/types/optional.h" - -namespace gvisor { -namespace testing { -namespace { - -std::atomic<absl::optional<bool>> cooperative_save_present; -std::atomic<absl::optional<bool>> random_save_present; - -bool CooperativeSavePresent() { - auto present = cooperative_save_present.load(); - if (!present.has_value()) { - present = getenv("GVISOR_COOPERATIVE_SAVE_TEST") != nullptr; - cooperative_save_present.store(present); - } - return present.value(); -} - -bool RandomSavePresent() { - auto present = random_save_present.load(); - if (!present.has_value()) { - present = getenv("GVISOR_RANDOM_SAVE_TEST") != nullptr; - random_save_present.store(present); - } - return present.value(); -} - -std::atomic<int> save_disable; - -} // namespace - -bool IsRunningWithSaveRestore() { - return CooperativeSavePresent() || RandomSavePresent(); -} - -void MaybeSave() { - if (CooperativeSavePresent() && save_disable.load() == 0) { - internal::DoCooperativeSave(); - } -} - -DisableSave::DisableSave() { save_disable++; } - -DisableSave::~DisableSave() { reset(); } - -void DisableSave::reset() { - if (!reset_) { - reset_ = true; - save_disable--; - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/save_util.h b/test/util/save_util.h deleted file mode 100644 index e7218ae88..000000000 --- a/test/util/save_util.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_SAVE_UTIL_H_ -#define GVISOR_TEST_UTIL_SAVE_UTIL_H_ - -namespace gvisor { -namespace testing { - -// Returns true if the environment in which the calling process is executing -// allows the test to be checkpointed and restored during execution. -bool IsRunningWithSaveRestore(); - -// May perform a co-operative save cycle. -// -// errno is guaranteed to be preserved. -void MaybeSave(); - -// Causes MaybeSave to become a no-op until destroyed or reset. -class DisableSave { - public: - DisableSave(); - ~DisableSave(); - DisableSave(DisableSave const&) = delete; - DisableSave(DisableSave&&) = delete; - DisableSave& operator=(DisableSave const&) = delete; - DisableSave& operator=(DisableSave&&) = delete; - - // reset allows saves to continue, and is called implicitly by the destructor. - // It may be called multiple times safely, but is not thread-safe. - void reset(); - - private: - bool reset_ = false; -}; - -namespace internal { - -// Causes a co-operative save cycle to occur. -// -// errno is guaranteed to be preserved. -void DoCooperativeSave(); - -} // namespace internal - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_SAVE_UTIL_H_ diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc deleted file mode 100644 index 57431b3ea..000000000 --- a/test/util/save_util_linux.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifdef __linux__ - -#include <errno.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/save_util.h" - -#if defined(__x86_64__) || defined(__i386__) -#define SYS_TRIGGER_SAVE SYS_create_module -#elif defined(__aarch64__) -#define SYS_TRIGGER_SAVE SYS_finit_module -#else -#error "Unknown architecture" -#endif - -namespace gvisor { -namespace testing { -namespace internal { - -void DoCooperativeSave() { - int orig_errno = errno; - // We use it to trigger saving the sentry state - // when this syscall is called. - // Notice: this needs to be a valid syscall - // that is not used in any of the syscall tests. - syscall(SYS_TRIGGER_SAVE, nullptr, 0); - errno = orig_errno; -} - -} // namespace internal -} // namespace testing -} // namespace gvisor - -#endif // __linux__ diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc deleted file mode 100644 index 7749ded76..000000000 --- a/test/util/save_util_other.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef __linux__ - -#include "test/util/logging.h" - -namespace gvisor { -namespace testing { -namespace internal { - -void DoCooperativeSave() { - TEST_CHECK_MSG(false, "DoCooperativeSave not implemented"); -} - -} // namespace internal -} // namespace testing -} // namespace gvisor - -#endif diff --git a/test/util/signal_util.cc b/test/util/signal_util.cc deleted file mode 100644 index 5ee95ee80..000000000 --- a/test/util/signal_util.cc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/signal_util.h" - -#include <signal.h> - -#include <ostream> - -#include "gtest/gtest.h" -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace { - -struct Range { - int start; - int end; -}; - -// Format a Range as "start-end" or "start" for single value Ranges. -static ::std::ostream& operator<<(::std::ostream& os, const Range& range) { - if (range.end > range.start) { - return os << range.start << '-' << range.end; - } - - return os << range.start; -} - -} // namespace - -// Format a sigset_t as a comma separated list of numeric ranges. -// Empty sigset: [] -// Full sigset: [1-31,34-64] -::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset) { - const char* delim = ""; - Range range = {0, 0}; - - os << '['; - - for (int sig = 1; sig <= gvisor::testing::kMaxSignal; ++sig) { - if (sigismember(&sigset, sig)) { - if (range.start) { - range.end = sig; - } else { - range.start = sig; - range.end = sig; - } - } else if (range.start) { - os << delim << range; - delim = ","; - range.start = 0; - range.end = 0; - } - } - - if (range.start) { - os << delim << range; - } - - return os << ']'; -} - -namespace gvisor { -namespace testing { - -PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa) { - struct sigaction old_sa; - int rc = sigaction(sig, &sa, &old_sa); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "sigaction failed"); - } - return Cleanup([sig, old_sa] { - EXPECT_THAT(sigaction(sig, &old_sa, nullptr), SyscallSucceeds()); - }); -} - -PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set) { - sigset_t old; - int rc = sigprocmask(how, &set, &old); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "sigprocmask failed"); - } - return Cleanup([old] { - EXPECT_THAT(sigprocmask(SIG_SETMASK, &old, nullptr), SyscallSucceeds()); - }); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/signal_util.h b/test/util/signal_util.h deleted file mode 100644 index 20eebd7e4..000000000 --- a/test/util/signal_util.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ -#define GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ - -#include <signal.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include <ostream> - -#include "gmock/gmock.h" -#include "test/util/cleanup.h" -#include "test/util/posix_error.h" - -// Format a sigset_t as a comma separated list of numeric ranges. -::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset); - -namespace gvisor { -namespace testing { - -// The maximum signal number. -static constexpr int kMaxSignal = 64; - -// Wrapper for the tgkill(2) syscall, which glibc does not provide. -inline int tgkill(pid_t tgid, pid_t tid, int sig) { - return syscall(__NR_tgkill, tgid, tid, sig); -} - -// Installs the passed sigaction and returns a cleanup function to restore the -// previous handler when it goes out of scope. -PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa); - -// Updates the signal mask as per sigprocmask(2) and returns a cleanup function -// to restore the previous signal mask when it goes out of scope. -PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set); - -// ScopedSignalMask variant that creates a mask of the single signal 'sig'. -inline PosixErrorOr<Cleanup> ScopedSignalMask(int how, int sig) { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, sig); - return ScopedSignalMask(how, set); -} - -// Asserts equality of two sigset_t values. -MATCHER_P(EqualsSigset, value, "equals " + ::testing::PrintToString(value)) { - for (int sig = 1; sig <= kMaxSignal; ++sig) { - if (sigismember(&arg, sig) != sigismember(&value, sig)) { - return false; - } - } - return true; -} - -#ifdef __x86_64__ -// Fault can be used to generate a synchronous SIGSEGV. -// -// This fault can be fixed up in a handler via fixup, below. -inline void Fault() { - // Zero and dereference %ax. - asm("movabs $0, %%rax\r\n" - "mov 0(%%rax), %%rax\r\n" - : - : - : "ax"); -} - -// FixupFault fixes up a fault generated by fault, above. -inline void FixupFault(ucontext_t* ctx) { - // Skip the bad instruction above. - // - // The encoding is 0x48 0xab 0x00. - ctx->uc_mcontext.gregs[REG_RIP] += 3; -} -#elif __aarch64__ -inline void Fault() { - // Zero and dereference x0. - asm("mov x0, xzr\r\n" - "str xzr, [x0]\r\n" - : - : - : "x0"); -} - -inline void FixupFault(ucontext_t* ctx) { - // Skip the bad instruction above. - ctx->uc_mcontext.pc += 4; -} -#endif - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc deleted file mode 100644 index e1bdee7fd..000000000 --- a/test/util/temp_path.cc +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/temp_path.h" - -#include <unistd.h> - -#include <atomic> -#include <cstdlib> -#include <iostream> - -#include "gtest/gtest.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -std::atomic<uint64_t> global_temp_file_number = ATOMIC_VAR_INIT(1); - -// Return a new temp filename, intended to be unique system-wide. -// -// The global file number helps maintain file naming consistency across -// different runs of a test. -// -// The timestamp is necessary because the test infrastructure invokes each -// test case in a separate process (resetting global_temp_file_number) and -// potentially in parallel, which allows for races between selecting and using a -// name. -std::string NextTempBasename() { - return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_", - absl::ToUnixNanos(absl::Now())); -} - -void TryDeleteRecursively(std::string const& path) { - if (!path.empty()) { - int undeleted_dirs = 0; - int undeleted_files = 0; - auto status = RecursivelyDelete(path, &undeleted_dirs, &undeleted_files); - if (undeleted_dirs || undeleted_files || !status.ok()) { - std::cerr << path << ": failed to delete " << undeleted_dirs - << " directories and " << undeleted_files - << " files: " << status << std::endl; - } - } -} - -} // namespace - -constexpr mode_t TempPath::kDefaultFileMode; -constexpr mode_t TempPath::kDefaultDirMode; - -std::string NewTempAbsPathInDir(absl::string_view const dir) { - return JoinPath(dir, NextTempBasename()); -} - -std::string NewTempAbsPath() { - return NewTempAbsPathInDir(GetAbsoluteTestTmpdir()); -} - -std::string NewTempRelPath() { return NextTempBasename(); } - -std::string GetAbsoluteTestTmpdir() { - // Note that TEST_TMPDIR is guaranteed to be set. - char* env_tmpdir = getenv("TEST_TMPDIR"); - std::string tmp_dir = - env_tmpdir != nullptr ? std::string(env_tmpdir) : "/tmp"; - - return MakeAbsolute(tmp_dir, "").ValueOrDie(); -} - -PosixErrorOr<TempPath> TempPath::CreateFileWith(absl::string_view const parent, - absl::string_view const content, - mode_t const mode) { - return CreateIn(parent, [=](absl::string_view path) -> PosixError { - // CreateWithContents will call open(O_WRONLY) with the given mode. If the - // mode is not user-writable, save/restore cannot preserve the fd. Hence - // the little permission dance that's done here. - auto res = CreateWithContents(path, content, mode | 0200); - RETURN_IF_ERRNO(res); - - return Chmod(path, mode); - }); -} - -PosixErrorOr<TempPath> TempPath::CreateDirWith(absl::string_view const parent, - mode_t const mode) { - return CreateIn(parent, - [=](absl::string_view path) { return Mkdir(path, mode); }); -} - -PosixErrorOr<TempPath> TempPath::CreateSymlinkTo(absl::string_view const parent, - std::string const& dest) { - return CreateIn(parent, [=](absl::string_view path) { - int ret = symlink(dest.c_str(), std::string(path).c_str()); - if (ret != 0) { - return PosixError(errno, "symlink failed"); - } - return NoError(); - }); -} - -PosixErrorOr<TempPath> TempPath::CreateFileIn(absl::string_view const parent) { - return TempPath::CreateFileWith(parent, absl::string_view(), - kDefaultFileMode); -} - -PosixErrorOr<TempPath> TempPath::CreateDirIn(absl::string_view const parent) { - return TempPath::CreateDirWith(parent, kDefaultDirMode); -} - -PosixErrorOr<TempPath> TempPath::CreateFileMode(mode_t mode) { - return TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), absl::string_view(), - mode); -} - -PosixErrorOr<TempPath> TempPath::CreateFile() { - return TempPath::CreateFileIn(GetAbsoluteTestTmpdir()); -} - -PosixErrorOr<TempPath> TempPath::CreateDir() { - return TempPath::CreateDirIn(GetAbsoluteTestTmpdir()); -} - -TempPath::~TempPath() { TryDeleteRecursively(path_); } - -TempPath::TempPath(TempPath&& orig) { reset(orig.release()); } - -TempPath& TempPath::operator=(TempPath&& orig) { - reset(orig.release()); - return *this; -} - -std::string TempPath::reset(std::string newpath) { - std::string path = path_; - TryDeleteRecursively(path_); - path_ = std::move(newpath); - return path; -} - -std::string TempPath::release() { - std::string path = path_; - path_ = std::string(); - return path; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/temp_path.h b/test/util/temp_path.h deleted file mode 100644 index 9e5ac11f4..000000000 --- a/test/util/temp_path.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_TEMP_PATH_H_ -#define GVISOR_TEST_UTIL_TEMP_PATH_H_ - -#include <sys/stat.h> - -#include <string> -#include <utility> - -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Returns an absolute path for a file in `dir` that does not yet exist. -// Distinct calls to NewTempAbsPathInDir from the same process, even from -// multiple threads, are guaranteed to return different paths. Distinct calls to -// NewTempAbsPathInDir from different processes are not synchronized. -std::string NewTempAbsPathInDir(absl::string_view const dir); - -// Like NewTempAbsPathInDir, but the returned path is in the test's temporary -// directory, as provided by the testing framework. -std::string NewTempAbsPath(); - -// Like NewTempAbsPathInDir, but the returned path is relative (to the current -// working directory). -std::string NewTempRelPath(); - -// Returns the absolute path for the test temp dir. -std::string GetAbsoluteTestTmpdir(); - -// Represents a temporary file or directory. -class TempPath { - public: - // Default creation mode for files. - static constexpr mode_t kDefaultFileMode = 0644; - - // Default creation mode for directories. - static constexpr mode_t kDefaultDirMode = 0755; - - // Creates a temporary file in directory `parent` with mode `mode` and - // contents `content`. - static PosixErrorOr<TempPath> CreateFileWith(absl::string_view parent, - absl::string_view content, - mode_t mode); - - // Creates an empty temporary subdirectory in directory `parent` with mode - // `mode`. - static PosixErrorOr<TempPath> CreateDirWith(absl::string_view parent, - mode_t mode); - - // Creates a temporary symlink in directory `parent` to destination `dest`. - static PosixErrorOr<TempPath> CreateSymlinkTo(absl::string_view parent, - std::string const& dest); - - // Creates an empty temporary file in directory `parent` with mode - // kDefaultFileMode. - static PosixErrorOr<TempPath> CreateFileIn(absl::string_view parent); - - // Creates an empty temporary subdirectory in directory `parent` with mode - // kDefaultDirMode. - static PosixErrorOr<TempPath> CreateDirIn(absl::string_view parent); - - // Creates an empty temporary file in the test's temporary directory with mode - // `mode`. - static PosixErrorOr<TempPath> CreateFileMode(mode_t mode); - - // Creates an empty temporary file in the test's temporary directory with - // mode kDefaultFileMode. - static PosixErrorOr<TempPath> CreateFile(); - - // Creates an empty temporary subdirectory in the test's temporary directory - // with mode kDefaultDirMode. - static PosixErrorOr<TempPath> CreateDir(); - - // Constructs a TempPath that represents nothing. - TempPath() = default; - - // Constructs a TempPath that represents the given path, which will be deleted - // when the TempPath is destroyed. - explicit TempPath(std::string path) : path_(std::move(path)) {} - - // Attempts to delete the represented temporary file or directory (in the - // latter case, also attempts to delete its contents). - ~TempPath(); - - // Attempts to delete the represented temporary file or directory, then - // transfers ownership of the path represented by orig to this TempPath. - TempPath(TempPath&& orig); - TempPath& operator=(TempPath&& orig); - - // Changes the path this TempPath represents. If the TempPath already - // represented a path, deletes and returns that path. Otherwise returns the - // empty string. - std::string reset(std::string newpath); - std::string reset() { return reset(""); } - - // Forgets and returns the path this TempPath represents. The path is not - // deleted. - std::string release(); - - // Returns the path this TempPath represents. - std::string path() const { return path_; } - - private: - template <typename F> - static PosixErrorOr<TempPath> CreateIn(absl::string_view const parent, - F const& f) { - std::string path = NewTempAbsPathInDir(parent); - RETURN_IF_ERRNO(f(path)); - return TempPath(std::move(path)); - } - - std::string path_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_TEMP_PATH_H_ diff --git a/test/util/temp_umask.h b/test/util/temp_umask.h deleted file mode 100644 index e7de84a54..000000000 --- a/test/util/temp_umask.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_TEMP_UMASK_H_ -#define GVISOR_TEST_UTIL_TEMP_UMASK_H_ - -#include <sys/stat.h> -#include <sys/types.h> - -namespace gvisor { -namespace testing { - -class TempUmask { - public: - // Sets the process umask to `mask`. - explicit TempUmask(mode_t mask) : old_mask_(umask(mask)) {} - - // Sets the process umask to its previous value. - ~TempUmask() { umask(old_mask_); } - - private: - mode_t old_mask_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_TEMP_UMASK_H_ diff --git a/test/util/test_main.cc b/test/util/test_main.cc deleted file mode 100644 index 1f389e58f..000000000 --- a/test/util/test_main.cc +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/test_util.h" - -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - return gvisor::testing::RunAllTests(); -} diff --git a/test/util/test_util.cc b/test/util/test_util.cc deleted file mode 100644 index d0c1d6426..000000000 --- a/test/util/test_util.cc +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/test_util.h" - -#include <limits.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <sys/utsname.h> -#include <unistd.h> - -#include <ctime> -#include <iostream> -#include <vector> - -#include "absl/base/attributes.h" -#include "absl/flags/flag.h" // IWYU pragma: keep -#include "absl/flags/parse.h" // IWYU pragma: keep -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "absl/time/time.h" -#include "test/util/fs_util.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -constexpr char kGvisorNetwork[] = "GVISOR_NETWORK"; -constexpr char kGvisorVfs[] = "GVISOR_VFS"; -constexpr char kFuseEnabled[] = "FUSE_ENABLED"; - -bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; } - -const std::string GvisorPlatform() { - // Set by runner.go. - const char* env = getenv(kTestOnGvisor); - if (!env) { - return Platform::kNative; - } - return std::string(env); -} - -bool IsRunningWithHostinet() { - const char* env = getenv(kGvisorNetwork); - return env && strcmp(env, "host") == 0; -} - -bool IsRunningWithVFS1() { - const char* env = getenv(kGvisorVfs); - if (env == nullptr) { - // If not set, it's running on Linux. - return false; - } - return strcmp(env, "VFS1") == 0; -} - -bool IsFUSEEnabled() { - const char* env = getenv(kFuseEnabled); - return env && strcmp(env, "TRUE") == 0; -} - -// Inline cpuid instruction. Preserve %ebx/%rbx register. In PIC compilations -// %ebx contains the address of the global offset table. %rbx is occasionally -// used to address stack variables in presence of dynamic allocas. -#if defined(__x86_64__) -#define GETCPUID(a, b, c, d, a_inp, c_inp) \ - asm("mov %%rbx, %%rdi\n" \ - "cpuid\n" \ - "xchg %%rdi, %%rbx\n" \ - : "=a"(a), "=D"(b), "=c"(c), "=d"(d) \ - : "a"(a_inp), "2"(c_inp)) - -CPUVendor GetCPUVendor() { - uint32_t eax, ebx, ecx, edx; - std::string vendor_str; - // Get vendor string (issue CPUID with eax = 0) - GETCPUID(eax, ebx, ecx, edx, 0, 0); - vendor_str.append(reinterpret_cast<char*>(&ebx), 4); - vendor_str.append(reinterpret_cast<char*>(&edx), 4); - vendor_str.append(reinterpret_cast<char*>(&ecx), 4); - if (vendor_str == "GenuineIntel") { - return CPUVendor::kIntel; - } else if (vendor_str == "AuthenticAMD") { - return CPUVendor::kAMD; - } - return CPUVendor::kUnknownVendor; -} -#endif // defined(__x86_64__) - -bool operator==(const KernelVersion& first, const KernelVersion& second) { - return first.major == second.major && first.minor == second.minor && - first.micro == second.micro; -} - -PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_str) { - KernelVersion version = {}; - std::vector<std::string> values = - absl::StrSplit(vers_str, absl::ByAnyChar(".-")); - if (values.size() == 2) { - ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0])); - ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1])); - return version; - } else if (values.size() >= 3) { - ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0])); - ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1])); - ASSIGN_OR_RETURN_ERRNO(version.micro, Atoi<int>(values[2])); - return version; - } - return PosixError(EINVAL, absl::StrCat("Unknown kernel release: ", vers_str)); -} - -PosixErrorOr<KernelVersion> GetKernelVersion() { - utsname buf; - RETURN_ERROR_IF_SYSCALL_FAIL(uname(&buf)); - return ParseKernelVersion(buf.release); -} - -std::string CPUSetToString(const cpu_set_t& set, size_t cpus) { - std::string str = "cpuset["; - for (unsigned int n = 0; n < cpus; n++) { - if (CPU_ISSET(n, &set)) { - if (n != 0) { - absl::StrAppend(&str, " "); - } - absl::StrAppend(&str, n); - } - } - absl::StrAppend(&str, "]"); - return str; -} - -// An overloaded operator<< makes it easy to dump the value of an OpenFd. -std::ostream& operator<<(std::ostream& out, OpenFd const& ofd) { - out << ofd.fd << " -> " << ofd.link; - return out; -} - -// An overloaded operator<< makes it easy to dump a vector of OpenFDs. -std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v) { - for (const auto& ofd : v) { - out << ofd << std::endl; - } - return out; -} - -PosixErrorOr<std::vector<OpenFd>> GetOpenFDs() { - // Get the results from /proc/self/fd. - ASSIGN_OR_RETURN_ERRNO(auto dir_list, - ListDir("/proc/self/fd", /*skipdots=*/true)); - - std::vector<OpenFd> ret_fds; - for (const auto& str_fd : dir_list) { - OpenFd open_fd = {}; - ASSIGN_OR_RETURN_ERRNO(open_fd.fd, Atoi<int>(str_fd)); - std::string path = absl::StrCat("/proc/self/fd/", open_fd.fd); - - // Resolve the link. - char buf[PATH_MAX] = {}; - int ret = readlink(path.c_str(), buf, sizeof(buf)); - if (ret < 0) { - if (errno == ENOENT) { - // The FD may have been closed, let's be resilient. - continue; - } - - return PosixError( - errno, absl::StrCat("readlink of ", path, " returned errno ", errno)); - } - open_fd.link = std::string(buf, ret); - ret_fds.emplace_back(std::move(open_fd)); - } - return ret_fds; -} - -PosixErrorOr<uint64_t> Links(const std::string& path) { - struct stat st; - if (stat(path.c_str(), &st)) { - return PosixError(errno, absl::StrCat("Failed to stat ", path)); - } - return static_cast<uint64_t>(st.st_nlink); -} - -void RandomizeBuffer(void* buffer, size_t len) { - struct timespec ts = {}; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint32_t seed = static_cast<uint32_t>(ts.tv_nsec); - char* const buf = static_cast<char*>(buffer); - for (size_t i = 0; i < len; i++) { - buf[i] = rand_r(&seed) % 255; - } -} - -std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size, - void* buf, - size_t buflen) { - std::vector<std::vector<struct iovec>> result; - for (uint64_t offset = 0; offset < total_size;) { - auto& iovec_array = *result.emplace(result.end()); - - for (; offset < total_size && iovec_array.size() < IOV_MAX; - offset += buflen) { - struct iovec iov = {}; - iov.iov_base = buf; - iov.iov_len = std::min<uint64_t>(total_size - offset, buflen); - iovec_array.push_back(iov); - } - } - - return result; -} - -uint64_t Megabytes(uint64_t n) { - // Overflow check, upper 20 bits in n shouldn't be set. - TEST_CHECK(!(0xfffff00000000000 & n)); - return n << 20; -} - -bool Equivalent(uint64_t current, uint64_t target, double tolerance) { - auto abs_diff = target > current ? target - current : current - target; - return abs_diff <= static_cast<uint64_t>(tolerance * target); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/test_util.h b/test/util/test_util.h deleted file mode 100644 index 876ff58db..000000000 --- a/test/util/test_util.h +++ /dev/null @@ -1,811 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Utilities for syscall testing. -// -// Initialization -// ============== -// -// Prior to calling RUN_ALL_TESTS, all tests must use TestInit(&argc, &argv). -// See the TestInit function for exact side-effects and semantics. -// -// Configuration -// ============= -// -// IsRunningOnGvisor returns true if the test is known to be running on gVisor. -// GvisorPlatform can be used to get more detail: -// -// if (GvisorPlatform() == Platform::kPtrace) { -// ... -// } -// -// SetupGvisorDeathTest ensures that signal handling does not interfere with -/// tests that rely on fatal signals. -// -// Matchers -// ======== -// -// ElementOf(xs) matches if the matched value is equal to an element of the -// container xs. Example: -// -// // PASS -// EXPECT_THAT(1, ElementOf({0, 1, 2})); -// -// // FAIL -// // Value of: 3 -// // Expected: one of {0, 1, 2} -// // Actual: 3 -// EXPECT_THAT(3, ElementOf({0, 1, 2})); -// -// SyscallSucceeds() matches if the syscall is successful. A successful syscall -// is defined by either a return value not equal to -1, or a return value of -1 -// with an errno of 0 (which is a possible successful return for e.g. -// PTRACE_PEEK). Example: -// -// // PASS -// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallSucceeds()); -// -// // FAIL -// // Value of: open("/", O_RDWR) -// // Expected: not -1 (success) -// // Actual: -1 (of type int), with errno 21 (Is a directory) -// EXPECT_THAT(open("/", O_RDWR), SyscallSucceeds()); -// -// SyscallSucceedsWithValue(m) matches if the syscall is successful, and the -// value also matches m. Example: -// -// // PASS -// EXPECT_THAT(read(4, buf, 8192), SyscallSucceedsWithValue(8192)); -// -// // FAIL -// // Value of: read(-1, buf, 8192) -// // Expected: is equal to 8192 -// // Actual: -1 (of type long), with errno 9 (Bad file number) -// EXPECT_THAT(read(-1, buf, 8192), SyscallSucceedsWithValue(8192)); -// -// // FAIL -// // Value of: read(4, buf, 1) -// // Expected: is > 4096 -// // Actual: 1 (of type long) -// EXPECT_THAT(read(4, buf, 1), SyscallSucceedsWithValue(Gt(4096))); -// -// SyscallFails() matches if the syscall is unsuccessful. An unsuccessful -// syscall is defined by a return value of -1 with a non-zero errno. Example: -// -// // PASS -// EXPECT_THAT(open("/", O_RDWR), SyscallFails()); -// -// // FAIL -// // Value of: open("/dev/null", O_RDONLY) -// // Expected: -1 (failure) -// // Actual: 0 (of type int) -// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFails()); -// -// SyscallFailsWithErrno(m) matches if the syscall is unsuccessful, and errno -// matches m. Example: -// -// // PASS -// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EISDIR)); -// -// // PASS -// EXPECT_THAT(open("/etc/passwd", O_RDWR | O_DIRECTORY), -// SyscallFailsWithErrno(AnyOf(EACCES, ENOTDIR))); -// -// // FAIL -// // Value of: open("/dev/null", O_RDONLY) -// // Expected: -1 (failure) with errno 21 (Is a directory) -// // Actual: 0 (of type int) -// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFailsWithErrno(EISDIR)); -// -// // FAIL -// // Value of: open("/", O_RDWR) -// // Expected: -1 (failure) with errno 22 (Invalid argument) -// // Actual: -1 (of type int), failure, but with errno 21 (Is a directory) -// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EINVAL)); -// -// Because the syscall matchers encode save/restore functionality, their meaning -// should not be inverted via Not. That is, AnyOf(SyscallSucceedsWithValue(1), -// SyscallSucceedsWithValue(2)) is permitted, but not -// Not(SyscallFailsWithErrno(EPERM)). -// -// Syscalls -// ======== -// -// RetryEINTR wraps a function that returns -1 and sets errno on failure -// to be automatically retried when EINTR occurs. Example: -// -// auto rv = RetryEINTR(waitpid)(pid, &status, 0); -// -// ReadFd/WriteFd/PreadFd/PwriteFd are interface-compatible wrappers around the -// read/write/pread/pwrite syscalls to handle both EINTR and partial -// reads/writes. Example: -// -// EXPECT_THAT(ReadFd(fd, &buf, size), SyscallSucceedsWithValue(size)); -// -// General Utilities -// ================= -// -// ApplyVec(f, xs) returns a vector containing the result of applying function -// `f` to each value in `xs`. -// -// AllBitwiseCombinations takes a variadic number of ranges containing integers -// and returns a vector containing every integer that can be formed by ORing -// together exactly one integer from each list. List<T> is an alias for -// std::initializer_list<T> that makes AllBitwiseCombinations more ergonomic to -// use with list literals (initializer lists do not otherwise participate in -// template argument deduction). Example: -// -// EXPECT_THAT( -// AllBitwiseCombinations<int>( -// List<int>{SOCK_DGRAM, SOCK_STREAM}, -// List<int>{0, SOCK_NONBLOCK}), -// Contains({SOCK_DGRAM, SOCK_STREAM, SOCK_DGRAM | SOCK_NONBLOCK, -// SOCK_STREAM | SOCK_NONBLOCK})); -// -// VecCat takes a variadic number of containers and returns a vector containing -// the concatenated contents. -// -// VecAppend takes an initial container and a variadic number of containers and -// appends each to the initial container. -// -// RandomizeBuffer will use MTRandom to fill the given buffer with random bytes. -// -// GenerateIovecs will return the smallest number of iovec arrays for writing a -// given total number of bytes to a file, each iovec array size up to IOV_MAX, -// each iovec in each array pointing to the same buffer. - -#ifndef GVISOR_TEST_UTIL_TEST_UTIL_H_ -#define GVISOR_TEST_UTIL_TEST_UTIL_H_ - -#include <stddef.h> -#include <stdlib.h> -#include <sys/uio.h> -#include <time.h> -#include <unistd.h> - -#include <algorithm> -#include <cerrno> -#include <initializer_list> -#include <iterator> -#include <string> -#include <thread> // NOLINT: using std::thread::hardware_concurrency(). -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/string_view.h" -#include "absl/time/time.h" -#include "test/util/fs_util.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" - -namespace gvisor { -namespace testing { - -constexpr char kTestOnGvisor[] = "TEST_ON_GVISOR"; - -// TestInit must be called prior to RUN_ALL_TESTS. -// -// This parses all arguments and adjusts argc and argv appropriately. -// -// TestInit may create background threads. -void TestInit(int* argc, char*** argv); - -// SKIP_IF may be used to skip a test case. -// -// These cases are still emitted, but a SKIPPED line will appear. -#define SKIP_IF(expr) \ - do { \ - if (expr) GTEST_SKIP() << #expr; \ - } while (0) - -// Platform contains platform names. -namespace Platform { -constexpr char kNative[] = "native"; -constexpr char kPtrace[] = "ptrace"; -constexpr char kKVM[] = "kvm"; -constexpr char kFuchsia[] = "fuchsia"; -} // namespace Platform - -bool IsRunningOnGvisor(); -const std::string GvisorPlatform(); -bool IsRunningWithHostinet(); -// TODO(gvisor.dev/issue/1624): Delete once VFS1 is gone. -bool IsRunningWithVFS1(); -bool IsFUSEEnabled(); - -#ifdef __linux__ -void SetupGvisorDeathTest(); -#endif - -struct KernelVersion { - int major; - int minor; - int micro; -}; - -bool operator==(const KernelVersion& first, const KernelVersion& second); - -PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_string); -PosixErrorOr<KernelVersion> GetKernelVersion(); - -static const size_t kPageSize = sysconf(_SC_PAGESIZE); - -enum class CPUVendor { kIntel, kAMD, kUnknownVendor }; - -CPUVendor GetCPUVendor(); - -inline int NumCPUs() { return std::thread::hardware_concurrency(); } - -// Converts cpu_set_t to a std::string for easy examination. -std::string CPUSetToString(const cpu_set_t& set, size_t cpus = CPU_SETSIZE); - -struct OpenFd { - // fd is the open file descriptor number. - int fd = -1; - - // link is the resolution of the symbolic link. - std::string link; -}; - -// Make it easier to log OpenFds to error streams. -std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v); -std::ostream& operator<<(std::ostream& out, OpenFd const& ofd); - -// Gets a detailed list of open fds for this process. -PosixErrorOr<std::vector<OpenFd>> GetOpenFDs(); - -// Returns the number of hard links to a path. -PosixErrorOr<uint64_t> Links(const std::string& path); - -inline uint64_t ms_elapsed(const struct timespec& begin, - const struct timespec& end) { - return (end.tv_sec - begin.tv_sec) * 1000 + - (end.tv_nsec - begin.tv_nsec) / 1000000; -} - -namespace internal { - -template <typename Container> -class ElementOfMatcher { - public: - explicit ElementOfMatcher(Container container) - : container_(::std::move(container)) {} - - template <typename T> - bool MatchAndExplain(T const& rv, - ::testing::MatchResultListener* const listener) const { - using std::count; - return count(container_.begin(), container_.end(), rv) != 0; - } - - void DescribeTo(::std::ostream* const os) const { - *os << "one of {"; - char const* sep = ""; - for (auto const& elem : container_) { - *os << sep << elem; - sep = ", "; - } - *os << "}"; - } - - void DescribeNegationTo(::std::ostream* const os) const { - *os << "none of {"; - char const* sep = ""; - for (auto const& elem : container_) { - *os << sep << elem; - sep = ", "; - } - *os << "}"; - } - - private: - Container const container_; -}; - -template <typename E> -class SyscallSuccessMatcher { - public: - explicit SyscallSuccessMatcher(E expected) - : expected_(::std::move(expected)) {} - - template <typename T> - operator ::testing::Matcher<T>() const { - // E is one of three things: - // - T, or a type losslessly and implicitly convertible to T. - // - A monomorphic Matcher<T>. - // - A polymorphic matcher. - // SafeMatcherCast handles any of the above correctly. - // - // Similarly, gMock will invoke this conversion operator to obtain a - // monomorphic matcher (this is how polymorphic matchers are implemented). - return ::testing::MakeMatcher( - new Impl<T>(::testing::SafeMatcherCast<T>(expected_))); - } - - private: - template <typename T> - class Impl : public ::testing::MatcherInterface<T> { - public: - explicit Impl(::testing::Matcher<T> matcher) - : matcher_(::std::move(matcher)) {} - - bool MatchAndExplain( - T const& rv, - ::testing::MatchResultListener* const listener) const override { - if (rv == static_cast<decltype(rv)>(-1) && errno != 0) { - *listener << "with errno " << PosixError(errno); - return false; - } - bool match = matcher_.MatchAndExplain(rv, listener); - if (match) { - MaybeSave(); - } - return match; - } - - void DescribeTo(::std::ostream* const os) const override { - matcher_.DescribeTo(os); - } - - void DescribeNegationTo(::std::ostream* const os) const override { - matcher_.DescribeNegationTo(os); - } - - private: - ::testing::Matcher<T> matcher_; - }; - - private: - E expected_; -}; - -// A polymorphic matcher equivalent to ::testing::internal::AnyMatcher, except -// not in namespace ::testing::internal, and describing SyscallSucceeds()'s -// match constraints (which are enforced by SyscallSuccessMatcher::Impl). -class AnySuccessValueMatcher { - public: - template <typename T> - operator ::testing::Matcher<T>() const { - return ::testing::MakeMatcher(new Impl<T>()); - } - - private: - template <typename T> - class Impl : public ::testing::MatcherInterface<T> { - public: - bool MatchAndExplain( - T const& rv, - ::testing::MatchResultListener* const listener) const override { - return true; - } - - void DescribeTo(::std::ostream* const os) const override { - *os << "not -1 (success)"; - } - - void DescribeNegationTo(::std::ostream* const os) const override { - *os << "-1 (failure)"; - } - }; -}; - -class SyscallFailureMatcher { - public: - explicit SyscallFailureMatcher(::testing::Matcher<int> errno_matcher) - : errno_matcher_(std::move(errno_matcher)) {} - - template <typename T> - bool MatchAndExplain(T const& rv, - ::testing::MatchResultListener* const listener) const { - if (rv != static_cast<decltype(rv)>(-1)) { - return false; - } - int actual_errno = errno; - *listener << "with errno " << PosixError(actual_errno); - bool match = errno_matcher_.MatchAndExplain(actual_errno, listener); - if (match) { - MaybeSave(); - } - return match; - } - - void DescribeTo(::std::ostream* const os) const { - *os << "-1 (failure), with errno "; - errno_matcher_.DescribeTo(os); - } - - void DescribeNegationTo(::std::ostream* const os) const { - *os << "not -1 (success), with errno "; - errno_matcher_.DescribeNegationTo(os); - } - - private: - ::testing::Matcher<int> errno_matcher_; -}; - -class SpecificErrnoMatcher : public ::testing::MatcherInterface<int> { - public: - explicit SpecificErrnoMatcher(int const expected) : expected_(expected) {} - - bool MatchAndExplain( - int const actual_errno, - ::testing::MatchResultListener* const listener) const override { - return actual_errno == expected_; - } - - void DescribeTo(::std::ostream* const os) const override { - *os << PosixError(expected_); - } - - void DescribeNegationTo(::std::ostream* const os) const override { - *os << "not " << PosixError(expected_); - } - - private: - int const expected_; -}; - -inline ::testing::Matcher<int> SpecificErrno(int const expected) { - return ::testing::MakeMatcher(new SpecificErrnoMatcher(expected)); -} - -} // namespace internal - -template <typename Container> -inline ::testing::PolymorphicMatcher<internal::ElementOfMatcher<Container>> -ElementOf(Container container) { - return ::testing::MakePolymorphicMatcher( - internal::ElementOfMatcher<Container>(::std::move(container))); -} - -template <typename T> -inline ::testing::PolymorphicMatcher< - internal::ElementOfMatcher<::std::vector<T>>> -ElementOf(::std::initializer_list<T> elems) { - return ::testing::MakePolymorphicMatcher( - internal::ElementOfMatcher<::std::vector<T>>(::std::vector<T>(elems))); -} - -template <typename E> -inline internal::SyscallSuccessMatcher<E> SyscallSucceedsWithValue(E expected) { - return internal::SyscallSuccessMatcher<E>(::std::move(expected)); -} - -inline internal::SyscallSuccessMatcher<internal::AnySuccessValueMatcher> -SyscallSucceeds() { - return SyscallSucceedsWithValue( - ::gvisor::testing::internal::AnySuccessValueMatcher()); -} - -inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> -SyscallFailsWithErrno(::testing::Matcher<int> expected) { - return ::testing::MakePolymorphicMatcher( - internal::SyscallFailureMatcher(::std::move(expected))); -} - -// Overload taking an int so that SyscallFailsWithErrno(<specific errno>) uses -// internal::SpecificErrno (which stringifies the errno) rather than -// ::testing::Eq (which doesn't). -inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> -SyscallFailsWithErrno(int const expected) { - return SyscallFailsWithErrno(internal::SpecificErrno(expected)); -} - -inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> -SyscallFails() { - return SyscallFailsWithErrno(::testing::Gt(0)); -} - -// As of GCC 7.2, -Wall => -Wc++17-compat => -Wnoexcept-type generates an -// irrelevant, non-actionable warning about ABI compatibility when -// RetryEINTRImpl is constructed with a noexcept function, such as glibc's -// syscall(). See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80985. -#if defined(__GNUC__) && !defined(__clang__) && \ - (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2)) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnoexcept-type" -#endif - -namespace internal { - -template <typename F> -struct RetryEINTRImpl { - F const f; - - explicit constexpr RetryEINTRImpl(F f) : f(std::move(f)) {} - - template <typename... Args> - auto operator()(Args&&... args) const - -> decltype(f(std::forward<Args>(args)...)) { - while (true) { - errno = 0; - auto const ret = f(std::forward<Args>(args)...); - if (ret != -1 || errno != EINTR) { - return ret; - } - } - } -}; - -} // namespace internal - -template <typename F> -constexpr internal::RetryEINTRImpl<F> RetryEINTR(F&& f) { - return internal::RetryEINTRImpl<F>(std::forward<F>(f)); -} - -#if defined(__GNUC__) && !defined(__clang__) && \ - (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2)) -#pragma GCC diagnostic pop -#endif - -namespace internal { - -template <typename F> -ssize_t ApplyFileIoSyscall(F const& f, size_t const count) { - size_t completed = 0; - // `do ... while` because some callers actually want to make a syscall with a - // count of 0. - do { - auto const cur = RetryEINTR(f)(completed); - if (cur < 0) { - return cur; - } else if (cur == 0) { - break; - } - completed += cur; - } while (completed < count); - return completed; -} - -} // namespace internal - -inline PosixErrorOr<std::string> ReadAllFd(int fd) { - std::string all; - all.reserve(128 * 1024); // arbitrary. - - std::vector<char> buffer(16 * 1024); - for (;;) { - auto const bytes = RetryEINTR(read)(fd, buffer.data(), buffer.size()); - if (bytes < 0) { - return PosixError(errno, "file read"); - } - if (bytes == 0) { - return std::move(all); - } - if (bytes > 0) { - all.append(buffer.data(), bytes); - } - } -} - -inline ssize_t ReadFd(int fd, void* buf, size_t count) { - return internal::ApplyFileIoSyscall( - [&](size_t completed) { - return read(fd, static_cast<char*>(buf) + completed, count - completed); - }, - count); -} - -inline ssize_t WriteFd(int fd, void const* buf, size_t count) { - return internal::ApplyFileIoSyscall( - [&](size_t completed) { - return write(fd, static_cast<char const*>(buf) + completed, - count - completed); - }, - count); -} - -inline ssize_t PreadFd(int fd, void* buf, size_t count, off_t offset) { - return internal::ApplyFileIoSyscall( - [&](size_t completed) { - return pread(fd, static_cast<char*>(buf) + completed, count - completed, - offset + completed); - }, - count); -} - -inline ssize_t PwriteFd(int fd, void const* buf, size_t count, off_t offset) { - return internal::ApplyFileIoSyscall( - [&](size_t completed) { - return pwrite(fd, static_cast<char const*>(buf) + completed, - count - completed, offset + completed); - }, - count); -} - -template <typename T> -using List = std::initializer_list<T>; - -namespace internal { - -template <typename T> -void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current) { - combinations->push_back(current); -} - -template <typename T, typename Arg, typename... Args> -void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current, - Arg&& next, Args&&... rest) { - for (auto const option : next) { - AppendAllBitwiseCombinations(combinations, current | option, rest...); - } -} - -inline size_t CombinedSize(size_t accum) { return accum; } - -template <typename T, typename... Args> -size_t CombinedSize(size_t accum, T const& x, Args&&... xs) { - return CombinedSize(accum + x.size(), std::forward<Args>(xs)...); -} - -// Base case: no more containers, so do nothing. -template <typename T> -void DoMoveExtendContainer(T* c) {} - -// Append each container next to c. -template <typename T, typename U, typename... Args> -void DoMoveExtendContainer(T* c, U&& next, Args&&... rest) { - std::move(std::begin(next), std::end(next), std::back_inserter(*c)); - DoMoveExtendContainer(c, std::forward<Args>(rest)...); -} - -} // namespace internal - -template <typename T = int> -std::vector<T> AllBitwiseCombinations() { - return std::vector<T>(); -} - -template <typename T = int, typename... Args> -std::vector<T> AllBitwiseCombinations(Args&&... args) { - std::vector<T> combinations; - internal::AppendAllBitwiseCombinations(&combinations, 0, args...); - return combinations; -} - -template <typename T, typename U, typename F> -std::vector<T> ApplyVec(F const& f, std::vector<U> const& us) { - std::vector<T> vec; - vec.reserve(us.size()); - for (auto const& u : us) { - vec.push_back(f(u)); - } - return vec; -} - -template <typename T, typename U> -std::vector<T> ApplyVecToVec(std::vector<std::function<T(U)>> const& fs, - std::vector<U> const& us) { - std::vector<T> vec; - vec.reserve(us.size() * fs.size()); - for (auto const& f : fs) { - for (auto const& u : us) { - vec.push_back(f(u)); - } - } - return vec; -} - -// Moves all elements from the containers `args` to the end of `c`. -template <typename T, typename... Args> -void VecAppend(T* c, Args&&... args) { - c->reserve(internal::CombinedSize(c->size(), args...)); - internal::DoMoveExtendContainer(c, std::forward<Args>(args)...); -} - -// Returns a vector containing the concatenated contents of the containers -// `args`. -template <typename T, typename... Args> -std::vector<T> VecCat(Args&&... args) { - std::vector<T> combined; - VecAppend(&combined, std::forward<Args>(args)...); - return combined; -} - -#define RETURN_ERROR_IF_SYSCALL_FAIL(syscall) \ - do { \ - if ((syscall) < 0 && errno != 0) { \ - return PosixError(errno, #syscall); \ - } \ - } while (false) - -// Fill the given buffer with random bytes. -void RandomizeBuffer(void* buffer, size_t len); - -template <typename T> -inline PosixErrorOr<T> Atoi(absl::string_view str) { - T ret; - if (!absl::SimpleAtoi<T>(str, &ret)) { - return PosixError(EINVAL, "String not a number."); - } - return ret; -} - -inline PosixErrorOr<uint64_t> AtoiBase(absl::string_view str, int base) { - if (base > 255 || base < 2) { - return PosixError(EINVAL, "Invalid Base"); - } - - uint64_t ret = 0; - if (!absl::numbers_internal::safe_strtou64_base(str, &ret, base)) { - return PosixError(EINVAL, "String not a number."); - } - - return ret; -} - -inline PosixErrorOr<double> Atod(absl::string_view str) { - double ret; - if (!absl::SimpleAtod(str, &ret)) { - return PosixError(EINVAL, "String not a double type."); - } - return ret; -} - -inline PosixErrorOr<float> Atof(absl::string_view str) { - float ret; - if (!absl::SimpleAtof(str, &ret)) { - return PosixError(EINVAL, "String not a float type."); - } - return ret; -} - -// Return the smallest number of iovec arrays that can be used to write -// "total_bytes" number of bytes, each iovec writing one "buf". -std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size, - void* buf, size_t buflen); - -// Returns bytes in 'n' megabytes. Used for readability. -uint64_t Megabytes(uint64_t n); - -// Predicate for checking that a value is within some tolerance of another -// value. Returns true iff current is in the range [target * (1 - tolerance), -// target * (1 + tolerance)]. -bool Equivalent(uint64_t current, uint64_t target, double tolerance); - -// Matcher wrapping the Equivalent predicate. -MATCHER_P2(EquivalentWithin, target, tolerance, - std::string(negation ? "Isn't" : "Is") + - ::absl::StrFormat(" within %.2f%% of the target of %zd bytes", - tolerance * 100, target)) { - if (target == 0) { - *result_listener << ::absl::StreamFormat("difference of infinity%%"); - } else { - int64_t delta = static_cast<int64_t>(arg) - static_cast<int64_t>(target); - double delta_percent = - static_cast<double>(delta) / static_cast<double>(target) * 100; - *result_listener << ::absl::StreamFormat("difference of %.2f%%", - delta_percent); - } - return Equivalent(arg, target, tolerance); -} - -// Returns the absolute path to the a data dependency. 'path' is the runfile -// location relative to workspace root. -#ifdef __linux__ -std::string RunfilePath(std::string path); -#endif - -void TestInit(int* argc, char*** argv); -int RunAllTests(void); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_TEST_UTIL_H_ diff --git a/test/util/test_util_impl.cc b/test/util/test_util_impl.cc deleted file mode 100644 index 7e1ad9e66..000000000 --- a/test/util/test_util_impl.cc +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <signal.h> - -#include "gtest/gtest.h" -#include "absl/flags/flag.h" -#include "absl/flags/parse.h" -#include "benchmark/benchmark.h" -#include "test/util/logging.h" - -extern bool FLAGS_benchmark_list_tests; -extern std::string FLAGS_benchmark_filter; - -namespace gvisor { -namespace testing { - -void SetupGvisorDeathTest() {} - -void TestInit(int* argc, char*** argv) { - ::testing::InitGoogleTest(argc, *argv); - benchmark::Initialize(argc, *argv); - ::absl::ParseCommandLine(*argc, *argv); - - // Always mask SIGPIPE as it's common and tests aren't expected to handle it. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0); -} - -int RunAllTests() { - if (FLAGS_benchmark_list_tests || FLAGS_benchmark_filter != ".") { - benchmark::RunSpecifiedBenchmarks(); - return 0; - } else { - return RUN_ALL_TESTS(); - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/test_util_runfiles.cc b/test/util/test_util_runfiles.cc deleted file mode 100644 index 7210094eb..000000000 --- a/test/util/test_util_runfiles.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <iostream> -#include <string> - -#include "test/util/fs_util.h" -#include "test/util/test_util.h" -#include "tools/cpp/runfiles/runfiles.h" - -namespace gvisor { -namespace testing { - -std::string RunfilePath(std::string path) { - static const bazel::tools::cpp::runfiles::Runfiles* const runfiles = [] { - std::string error; - auto* runfiles = - bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error); - if (runfiles == nullptr) { - std::cerr << "Unable to find runfiles: " << error << std::endl; - } - return runfiles; - }(); - - if (!runfiles) { - // Can't find runfiles? This probably won't work, but __main__/path is our - // best guess. - return JoinPath("__main__", path); - } - - return runfiles->Rlocation(JoinPath("__main__", path)); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/test_util_test.cc b/test/util/test_util_test.cc deleted file mode 100644 index f42100374..000000000 --- a/test/util/test_util_test.cc +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/test_util.h" - -#include <errno.h> - -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using ::testing::AnyOf; -using ::testing::Gt; -using ::testing::IsEmpty; -using ::testing::Lt; -using ::testing::Not; -using ::testing::TypedEq; -using ::testing::UnorderedElementsAre; -using ::testing::UnorderedElementsAreArray; - -namespace gvisor { -namespace testing { - -namespace { - -TEST(KernelVersionParsing, ValidateParsing) { - KernelVersion v = ASSERT_NO_ERRNO_AND_VALUE( - ParseKernelVersion("4.18.10-1foo2-amd64 baz blah")); - ASSERT_TRUE(v == KernelVersion({4, 18, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-1foo2-amd64")); - ASSERT_TRUE(v == KernelVersion({4, 18, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-14-amd64")); - ASSERT_TRUE(v == KernelVersion({4, 18, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-amd64")); - ASSERT_TRUE(v == KernelVersion({4, 18, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10")); - ASSERT_TRUE(v == KernelVersion({4, 18, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0.10")); - ASSERT_TRUE(v == KernelVersion({4, 0, 10})); - - v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0")); - ASSERT_TRUE(v == KernelVersion({4, 0, 0})); - - ASSERT_THAT(ParseKernelVersion("4.a"), PosixErrorIs(EINVAL, ::testing::_)); - ASSERT_THAT(ParseKernelVersion("3"), PosixErrorIs(EINVAL, ::testing::_)); - ASSERT_THAT(ParseKernelVersion(""), PosixErrorIs(EINVAL, ::testing::_)); - ASSERT_THAT(ParseKernelVersion("version 3.3.10"), - PosixErrorIs(EINVAL, ::testing::_)); -} - -TEST(MatchersTest, SyscallSucceeds) { - EXPECT_THAT(0, SyscallSucceeds()); - EXPECT_THAT(0L, SyscallSucceeds()); - - errno = 0; - EXPECT_THAT(-1, SyscallSucceeds()); - EXPECT_THAT(-1L, SyscallSucceeds()); - - errno = ENOMEM; - EXPECT_THAT(-1, Not(SyscallSucceeds())); - EXPECT_THAT(-1L, Not(SyscallSucceeds())); -} - -TEST(MatchersTest, SyscallSucceedsWithValue) { - EXPECT_THAT(0, SyscallSucceedsWithValue(0)); - EXPECT_THAT(1, SyscallSucceedsWithValue(Lt(3))); - EXPECT_THAT(-1, Not(SyscallSucceedsWithValue(Lt(3)))); - EXPECT_THAT(4, Not(SyscallSucceedsWithValue(Lt(3)))); - - // Non-int -1 - EXPECT_THAT(-1L, Not(SyscallSucceedsWithValue(0))); - - // Non-int, truncates to -1 if converted to int, with expected value - EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(0xffffffffL)); - - // Non-int, truncates to -1 if converted to int, with monomorphic matcher - EXPECT_THAT(0xffffffffL, - SyscallSucceedsWithValue(TypedEq<long>(0xffffffffL))); - - // Non-int, truncates to -1 if converted to int, with polymorphic matcher - EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(Gt(1))); -} - -TEST(MatchersTest, SyscallFails) { - EXPECT_THAT(0, Not(SyscallFails())); - EXPECT_THAT(0L, Not(SyscallFails())); - - errno = 0; - EXPECT_THAT(-1, Not(SyscallFails())); - EXPECT_THAT(-1L, Not(SyscallFails())); - - errno = ENOMEM; - EXPECT_THAT(-1, SyscallFails()); - EXPECT_THAT(-1L, SyscallFails()); -} - -TEST(MatchersTest, SyscallFailsWithErrno) { - EXPECT_THAT(0, Not(SyscallFailsWithErrno(EINVAL))); - EXPECT_THAT(0L, Not(SyscallFailsWithErrno(EINVAL))); - - errno = ENOMEM; - EXPECT_THAT(-1, Not(SyscallFailsWithErrno(EINVAL))); - EXPECT_THAT(-1L, Not(SyscallFailsWithErrno(EINVAL))); - - errno = EINVAL; - EXPECT_THAT(-1, SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(-1L, SyscallFailsWithErrno(EINVAL)); - - EXPECT_THAT(-1, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM))); - EXPECT_THAT(-1L, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM))); - - std::vector<int> expected_errnos({EINVAL, ENOMEM}); - errno = ENOMEM; - EXPECT_THAT(-1, SyscallFailsWithErrno(ElementOf(expected_errnos))); - EXPECT_THAT(-1L, SyscallFailsWithErrno(ElementOf(expected_errnos))); -} - -TEST(AllBitwiseCombinationsTest, NoArguments) { - EXPECT_THAT(AllBitwiseCombinations(), IsEmpty()); -} - -TEST(AllBitwiseCombinationsTest, EmptyList) { - EXPECT_THAT(AllBitwiseCombinations(List<int>{}), IsEmpty()); -} - -TEST(AllBitwiseCombinationsTest, SingleElementList) { - EXPECT_THAT(AllBitwiseCombinations(List<int>{5}), UnorderedElementsAre(5)); -} - -TEST(AllBitwiseCombinationsTest, SingleList) { - EXPECT_THAT(AllBitwiseCombinations(List<int>{0, 1, 2, 4}), - UnorderedElementsAre(0, 1, 2, 4)); -} - -TEST(AllBitwiseCombinationsTest, MultipleLists) { - EXPECT_THAT( - AllBitwiseCombinations(List<int>{0, 1, 2, 3}, List<int>{0, 4, 8, 12}), - UnorderedElementsAreArray( - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); -} - -TEST(RandomizeBuffer, Works) { - const std::vector<char> original(4096); - std::vector<char> buffer = original; - RandomizeBuffer(buffer.data(), buffer.size()); - EXPECT_NE(buffer, original); -} - -// Enable comparison of vectors of iovec arrays for the following test. -MATCHER_P(IovecsListEq, expected, "") { - if (arg.size() != expected.size()) { - *result_listener << "sizes are different (actual: " << arg.size() - << ", expected: " << expected.size() << ")"; - return false; - } - - for (uint64_t i = 0; i < expected.size(); ++i) { - const std::vector<struct iovec>& actual_iovecs = arg[i]; - const std::vector<struct iovec>& expected_iovecs = expected[i]; - if (actual_iovecs.size() != expected_iovecs.size()) { - *result_listener << "iovec array size at position " << i - << " is different (actual: " << actual_iovecs.size() - << ", expected: " << expected_iovecs.size() << ")"; - return false; - } - - for (uint64_t j = 0; j < expected_iovecs.size(); ++j) { - const struct iovec& actual_iov = actual_iovecs[j]; - const struct iovec& expected_iov = expected_iovecs[j]; - if (actual_iov.iov_base != expected_iov.iov_base) { - *result_listener << "iovecs in array " << i << " at position " << j - << " are different (expected iov_base: " - << expected_iov.iov_base - << ", got: " << actual_iov.iov_base << ")"; - return false; - } - if (actual_iov.iov_len != expected_iov.iov_len) { - *result_listener << "iovecs in array " << i << " at position " << j - << " are different (expected iov_len: " - << expected_iov.iov_len - << ", got: " << actual_iov.iov_len << ")"; - return false; - } - } - } - - return true; -} - -// Verify empty iovec list generation. -TEST(GenerateIovecs, EmptyList) { - std::vector<char> buffer = {'a', 'b', 'c'}; - - EXPECT_THAT(GenerateIovecs(0, buffer.data(), buffer.size()), - IovecsListEq(std::vector<std::vector<struct iovec>>())); -} - -// Verify generating a single array of only one, partial, iovec. -TEST(GenerateIovecs, OneArray) { - std::vector<char> buffer = {'a', 'b', 'c'}; - - std::vector<std::vector<struct iovec>> expected; - struct iovec iov = {}; - iov.iov_base = buffer.data(); - iov.iov_len = 2; - expected.push_back(std::vector<struct iovec>({iov})); - EXPECT_THAT(GenerateIovecs(2, buffer.data(), buffer.size()), - IovecsListEq(expected)); -} - -// Verify that it wraps around after IOV_MAX iovecs. -TEST(GenerateIovecs, WrapsAtIovMax) { - std::vector<char> buffer = {'a', 'b', 'c'}; - - std::vector<std::vector<struct iovec>> expected; - struct iovec iov = {}; - iov.iov_base = buffer.data(); - iov.iov_len = buffer.size(); - expected.emplace_back(); - for (int i = 0; i < IOV_MAX; ++i) { - expected[0].push_back(iov); - } - iov.iov_len = 1; - expected.push_back(std::vector<struct iovec>({iov})); - - EXPECT_THAT( - GenerateIovecs(IOV_MAX * buffer.size() + 1, buffer.data(), buffer.size()), - IovecsListEq(expected)); -} - -} // namespace - -} // namespace testing -} // namespace gvisor diff --git a/test/util/thread_util.h b/test/util/thread_util.h deleted file mode 100644 index 923c4fe10..000000000 --- a/test/util/thread_util.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_THREAD_UTIL_H_ -#define GVISOR_TEST_UTIL_THREAD_UTIL_H_ - -#include <pthread.h> -#ifdef __linux__ -#include <sys/syscall.h> -#endif -#include <unistd.h> - -#include <functional> -#include <utility> - -#include "test/util/logging.h" - -namespace gvisor { -namespace testing { - -// ScopedThread is a minimal wrapper around pthreads. -// -// This is used in lieu of more complex mechanisms because it provides very -// predictable behavior (no messing with timers, etc.) The thread will -// automatically joined when it is destructed (goes out of scope), but can be -// joined manually as well. -class ScopedThread { - public: - // Constructs a thread that executes f exactly once. - explicit ScopedThread(std::function<void*()> f) : f_(std::move(f)) { - CreateThread(); - } - - explicit ScopedThread(const std::function<void()>& f) { - f_ = [=] { - f(); - return nullptr; - }; - CreateThread(); - } - - ScopedThread(const ScopedThread& other) = delete; - ScopedThread& operator=(const ScopedThread& other) = delete; - - // Joins the thread. - ~ScopedThread() { Join(); } - - // Waits until this thread has finished executing. Join is idempotent and may - // be called multiple times, however Join itself is not thread-safe. - void* Join() { - if (!joined_) { - TEST_PCHECK(pthread_join(pt_, &retval_) == 0); - joined_ = true; - } - return retval_; - } - - private: - void CreateThread() { - TEST_PCHECK_MSG(pthread_create( - &pt_, /* attr = */ nullptr, - +[](void* arg) -> void* { - return static_cast<ScopedThread*>(arg)->f_(); - }, - this) == 0, - "thread creation failed"); - } - - std::function<void*()> f_; - pthread_t pt_; - bool joined_ = false; - void* retval_ = nullptr; -}; - -#ifdef __linux__ -inline pid_t gettid() { return syscall(SYS_gettid); } -#endif - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_THREAD_UTIL_H_ diff --git a/test/util/time_util.cc b/test/util/time_util.cc deleted file mode 100644 index 1ddfbfc9c..000000000 --- a/test/util/time_util.cc +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/time_util.h" - -#include <sys/syscall.h> -#include <unistd.h> - -#include "absl/time/time.h" - -namespace gvisor { -namespace testing { - -void SleepSafe(absl::Duration duration) { - if (duration == absl::ZeroDuration()) { - return; - } - - struct timespec ts = absl::ToTimespec(duration); - int ret; - while (1) { - ret = syscall(__NR_nanosleep, &ts, &ts); - if (ret == 0 || (ret <= 0 && errno != EINTR)) { - break; - } - } -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/time_util.h b/test/util/time_util.h deleted file mode 100644 index f3ddc9fde..000000000 --- a/test/util/time_util.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_TIME_UTIL_H_ -#define GVISOR_TEST_UTIL_TIME_UTIL_H_ - -#include "absl/time/time.h" - -namespace gvisor { -namespace testing { - -// Sleep for at least the specified duration. Avoids glibc. -void SleepSafe(absl::Duration duration); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_TIME_UTIL_H_ diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc deleted file mode 100644 index 75cfc4f40..000000000 --- a/test/util/timer_util.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/timer_util.h" - -namespace gvisor { -namespace testing { - -absl::Time Now(clockid_t id) { - struct timespec now; - TEST_PCHECK(clock_gettime(id, &now) == 0); - return absl::TimeFromTimespec(now); -} - -#ifdef __linux__ - -PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, - const struct sigevent& sev) { - int timerid; - int ret = syscall(SYS_timer_create, clockid, &sev, &timerid); - if (ret < 0) { - return PosixError(errno, "timer_create"); - } - if (ret > 0) { - return PosixError(EINVAL, "timer_create should never return positive"); - } - MaybeSave(); - return IntervalTimer(timerid); -} - -#endif // __linux__ - -} // namespace testing -} // namespace gvisor diff --git a/test/util/timer_util.h b/test/util/timer_util.h deleted file mode 100644 index e389108ef..000000000 --- a/test/util/timer_util.h +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_TIMER_UTIL_H_ -#define GVISOR_TEST_UTIL_TIMER_UTIL_H_ - -#include <errno.h> -#ifdef __linux__ -#include <sys/syscall.h> -#endif -#include <sys/time.h> - -#include <functional> - -#include "gmock/gmock.h" -#include "absl/time/time.h" -#include "test/util/cleanup.h" -#include "test/util/logging.h" -#include "test/util/posix_error.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -// From Linux's include/uapi/asm-generic/siginfo.h. -#ifndef sigev_notify_thread_id -#define sigev_notify_thread_id _sigev_un._tid -#endif - -// Returns the current time. -absl::Time Now(clockid_t id); - -// MonotonicTimer is a simple timer that uses a monotonic clock. -class MonotonicTimer { - public: - MonotonicTimer() {} - absl::Duration Duration() { - struct timespec ts; - TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - return absl::TimeFromTimespec(ts) - start_; - } - - void Start() { - struct timespec ts; - TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - start_ = absl::TimeFromTimespec(ts); - } - - protected: - absl::Time start_; -}; - -// Sets the given itimer and returns a cleanup function that restores the -// previous itimer when it goes out of scope. -inline PosixErrorOr<Cleanup> ScopedItimer(int which, - struct itimerval const& new_value) { - struct itimerval old_value; - int rc = setitimer(which, &new_value, &old_value); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "setitimer failed"); - } - return Cleanup(std::function<void(void)>([which, old_value] { - EXPECT_THAT(setitimer(which, &old_value, nullptr), SyscallSucceeds()); - })); -} - -#ifdef __linux__ - -// RAII type for a kernel "POSIX" interval timer. (The kernel provides system -// calls such as timer_create that behave very similarly, but not identically, -// to those described by timer_create(2); in particular, the kernel does not -// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on -// these kernel interval timers.) -// -// Compare implementation to FileDescriptor. -class IntervalTimer { - public: - IntervalTimer() = default; - - explicit IntervalTimer(int id) { set_id(id); } - - IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} - - IntervalTimer& operator=(IntervalTimer&& orig) { - if (this == &orig) return *this; - reset(orig.release()); - return *this; - } - - IntervalTimer(const IntervalTimer& other) = delete; - IntervalTimer& operator=(const IntervalTimer& other) = delete; - - ~IntervalTimer() { reset(); } - - int get() const { return id_; } - - int release() { - int const id = id_; - id_ = -1; - return id; - } - - void reset() { reset(-1); } - - void reset(int id) { - if (id_ >= 0) { - TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); - MaybeSave(); - } - set_id(id); - } - - PosixErrorOr<struct itimerspec> Set( - int flags, const struct itimerspec& new_value) const { - struct itimerspec old_value = {}; - if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { - return PosixError(errno, "timer_settime"); - } - MaybeSave(); - return old_value; - } - - PosixErrorOr<struct itimerspec> Get() const { - struct itimerspec curr_value = {}; - if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { - return PosixError(errno, "timer_gettime"); - } - MaybeSave(); - return curr_value; - } - - PosixErrorOr<int> Overruns() const { - int rv = syscall(SYS_timer_getoverrun, id_); - if (rv < 0) { - return PosixError(errno, "timer_getoverrun"); - } - MaybeSave(); - return rv; - } - - private: - void set_id(int id) { id_ = std::max(id, -1); } - - // Kernel timer_t is int; glibc timer_t is void*. - int id_ = -1; -}; - -// A wrapper around timer_create(2). -PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, - const struct sigevent& sev); - -#endif // __linux__ - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_TIMER_UTIL_H_ diff --git a/test/util/uid_util.cc b/test/util/uid_util.cc deleted file mode 100644 index b131b4b99..000000000 --- a/test/util/uid_util.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/posix_error.h" -#include "test/util/save_util.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<bool> IsRoot() { - uid_t ruid, euid, suid; - int rc = getresuid(&ruid, &euid, &suid); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "getresuid"); - } - if (ruid != 0 || euid != 0 || suid != 0) { - return false; - } - gid_t rgid, egid, sgid; - rc = getresgid(&rgid, &egid, &sgid); - MaybeSave(); - if (rc < 0) { - return PosixError(errno, "getresgid"); - } - if (rgid != 0 || egid != 0 || sgid != 0) { - return false; - } - return true; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/uid_util.h b/test/util/uid_util.h deleted file mode 100644 index 2cd387fb0..000000000 --- a/test/util/uid_util.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_SYSCALLS_UID_UTIL_H_ -#define GVISOR_TEST_SYSCALLS_UID_UTIL_H_ - -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Returns true if the caller's real/effective/saved user/group IDs are all 0. -PosixErrorOr<bool> IsRoot(); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_SYSCALLS_UID_UTIL_H_ |