diff options
Diffstat (limited to 'test')
108 files changed, 2931 insertions, 1017 deletions
diff --git a/test/benchmarks/BUILD b/test/benchmarks/BUILD new file mode 100644 index 000000000..faf310676 --- /dev/null +++ b/test/benchmarks/BUILD @@ -0,0 +1,11 @@ +load("//tools:defs.bzl", "bzl_library") + +package(licenses = ["notice"]) + +bzl_library( + name = "defs_bzl", + srcs = ["defs.bzl"], + visibility = [ + "//:sandbox", + ], +) diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD index b4b55317b..697ab5837 100644 --- a/test/benchmarks/base/BUILD +++ b/test/benchmarks/base/BUILD @@ -1,4 +1,5 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) @@ -14,7 +15,7 @@ go_library( ], ) -go_test( +benchmark_test( name = "startup_test", size = "enormous", srcs = ["startup_test.go"], @@ -26,7 +27,7 @@ go_test( ], ) -go_test( +benchmark_test( name = "size_test", size = "enormous", srcs = ["size_test.go"], @@ -39,7 +40,7 @@ go_test( ], ) -go_test( +benchmark_test( name = "sysbench_test", size = "enormous", srcs = ["sysbench_test.go"], diff --git a/test/benchmarks/database/BUILD b/test/benchmarks/database/BUILD index 93b380e8a..0b1743603 100644 --- a/test/benchmarks/database/BUILD +++ b/test/benchmarks/database/BUILD @@ -1,4 +1,5 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) @@ -6,19 +7,13 @@ go_library( name = "database", testonly = 1, srcs = ["database.go"], - deps = ["//test/benchmarks/harness"], ) -go_test( - name = "database_test", +benchmark_test( + name = "redis_test", size = "enormous", srcs = ["redis_test.go"], library = ":database", - tags = [ - # Requires docker and runsc to be configured before test runs. - "manual", - "local", - ], visibility = ["//:sandbox"], deps = [ "//pkg/test/dockerutil", diff --git a/test/benchmarks/database/database.go b/test/benchmarks/database/database.go index 9eeb59f9a..c15ca661c 100644 --- a/test/benchmarks/database/database.go +++ b/test/benchmarks/database/database.go @@ -14,18 +14,3 @@ // Package database holds benchmarks around database applications. package database - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package database. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go index 02e67154e..f8075a04b 100644 --- a/test/benchmarks/database/redis_test.go +++ b/test/benchmarks/database/redis_test.go @@ -16,6 +16,7 @@ package database import ( "context" + "os" "testing" "time" @@ -24,6 +25,8 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + // All possible operations from redis. Note: "ping" will // run both PING_INLINE and PING_BUILD. var operations []string = []string{ @@ -111,21 +114,23 @@ func BenchmarkRedis(b *testing.B) { // Reset profiles and timer to begin the measurement. server.RestartProfiles() b.ResetTimer() - for i := 0; i < b.N; i++ { - client := clientMachine.GetNativeContainer(ctx, b) - defer client.CleanUp(ctx) - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }, redis.MakeCmd(ip, serverPort)...) - if err != nil { - b.Fatalf("redis-benchmark failed with: %v", err) - } - - // Stop time while we parse results. - b.StopTimer() - redis.Report(b, out) - b.StartTimer() + client := clientMachine.GetNativeContainer(ctx, b) + defer client.CleanUp(ctx) + out, err := client.Run(ctx, dockerutil.RunOpts{ + Image: "benchmarks/redis", + }, redis.MakeCmd(ip, serverPort, b.N /*requests*/)...) + if err != nil { + b.Fatalf("redis-benchmark failed with: %v", err) } + + // Stop time while we parse results. + b.StopTimer() + redis.Report(b, out) }) } } + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/defs.bzl b/test/benchmarks/defs.bzl new file mode 100644 index 000000000..ef44b46e3 --- /dev/null +++ b/test/benchmarks/defs.bzl @@ -0,0 +1,14 @@ +"""Defines a rule for benchmark test targets.""" + +load("//tools:defs.bzl", "go_test") + +def benchmark_test(name, tags = [], **kwargs): + go_test( + name, + tags = [ + # Requires docker and runsc to be configured before the test runs. + "local", + "manual", + ], + **kwargs + ) diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD index 021fae38d..b4f967441 100644 --- a/test/benchmarks/fs/BUILD +++ b/test/benchmarks/fs/BUILD @@ -1,8 +1,8 @@ -load("//tools:defs.bzl", "go_test") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) -go_test( +benchmark_test( name = "bazel_test", size = "enormous", srcs = ["bazel_test.go"], @@ -14,7 +14,7 @@ go_test( ], ) -go_test( +benchmark_test( name = "fio_test", size = "enormous", srcs = ["fio_test.go"], diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go index 53ed3f9f2..3fb4da9d1 100644 --- a/test/benchmarks/fs/bazel_test.go +++ b/test/benchmarks/fs/bazel_test.go @@ -61,10 +61,10 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { for _, bm := range benchmarks { pageCache := tools.Parameter{ Name: "page_cache", - Value: "clean", + Value: "dirty", } if bm.clearCache { - pageCache.Value = "dirty" + pageCache.Value = "clean" } filesystem := tools.Parameter{ @@ -129,12 +129,14 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { if !strings.Contains(got, want) { b.Fatalf("string %s not in: %s", want, got) } - // Clean bazel in case we use b.N. - _, err = container.Exec(ctx, dockerutil.ExecOpts{ - WorkDir: prefix + workdir, - }, "bazel", "clean") - if err != nil { - b.Fatalf("build failed with: %v", err) + + // Clean bazel in the case we are doing another run. + if i < b.N-1 { + if _, err = container.Exec(ctx, dockerutil.ExecOpts{ + WorkDir: prefix + workdir, + }, "bazel", "clean"); err != nil { + b.Fatalf("build failed with: %v", err) + } } b.StartTimer() } diff --git a/test/benchmarks/harness/harness.go b/test/benchmarks/harness/harness.go index 5c9d0e01e..4c6e724aa 100644 --- a/test/benchmarks/harness/harness.go +++ b/test/benchmarks/harness/harness.go @@ -39,7 +39,7 @@ func (h *Harness) Init() error { flag.PrintDefaults() } flag.Parse() - if flag.NFlag() == 0 || *help { + if *help { flag.Usage() os.Exit(0) } diff --git a/test/benchmarks/media/BUILD b/test/benchmarks/media/BUILD index bb242d385..380783f0b 100644 --- a/test/benchmarks/media/BUILD +++ b/test/benchmarks/media/BUILD @@ -1,4 +1,5 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) @@ -6,12 +7,11 @@ go_library( name = "media", testonly = 1, srcs = ["media.go"], - deps = ["//test/benchmarks/harness"], ) -go_test( - name = "media_test", - size = "large", +benchmark_test( + name = "ffmpeg_test", + size = "enormous", srcs = ["ffmpeg_test.go"], library = ":media", visibility = ["//:sandbox"], diff --git a/test/benchmarks/media/ffmpeg_test.go b/test/benchmarks/media/ffmpeg_test.go index 7822dfad7..a462ec2a6 100644 --- a/test/benchmarks/media/ffmpeg_test.go +++ b/test/benchmarks/media/ffmpeg_test.go @@ -15,6 +15,7 @@ package media import ( "context" + "os" "strings" "testing" @@ -22,6 +23,8 @@ import ( "gvisor.dev/gvisor/test/benchmarks/harness" ) +var h harness.Harness + // BenchmarkFfmpeg runs ffmpeg in a container and records runtime. // BenchmarkFfmpeg should run as root to drop caches. func BenchmarkFfmpeg(b *testing.B) { @@ -32,13 +35,13 @@ func BenchmarkFfmpeg(b *testing.B) { defer machine.CleanUp() ctx := context.Background() - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) cmd := strings.Split("ffmpeg -i video.mp4 -c:v libx264 -preset veryslow output.mp4", " ") b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() + container := machine.GetContainer(ctx, b) + defer container.CleanUp(ctx) if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches: %v. You probably need root.", err) } @@ -51,3 +54,8 @@ func BenchmarkFfmpeg(b *testing.B) { } } } + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/media/media.go b/test/benchmarks/media/media.go index c7b35b758..ed7b24651 100644 --- a/test/benchmarks/media/media.go +++ b/test/benchmarks/media/media.go @@ -14,18 +14,3 @@ // Package media holds benchmarks around media processing applications. package media - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package media. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/ml/BUILD b/test/benchmarks/ml/BUILD index 970f52706..285ec35d9 100644 --- a/test/benchmarks/ml/BUILD +++ b/test/benchmarks/ml/BUILD @@ -1,4 +1,5 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) @@ -6,12 +7,11 @@ go_library( name = "ml", testonly = 1, srcs = ["ml.go"], - deps = ["//test/benchmarks/harness"], ) -go_test( - name = "ml_test", - size = "large", +benchmark_test( + name = "tensorflow_test", + size = "enormous", srcs = ["tensorflow_test.go"], library = ":ml", visibility = ["//:sandbox"], diff --git a/test/benchmarks/ml/ml.go b/test/benchmarks/ml/ml.go index 13282d7bb..d5fc5b7da 100644 --- a/test/benchmarks/ml/ml.go +++ b/test/benchmarks/ml/ml.go @@ -14,18 +14,3 @@ // Package ml holds benchmarks around machine learning performance. package ml - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package ml. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/ml/tensorflow_test.go b/test/benchmarks/ml/tensorflow_test.go index f7746897d..a55329d82 100644 --- a/test/benchmarks/ml/tensorflow_test.go +++ b/test/benchmarks/ml/tensorflow_test.go @@ -15,12 +15,15 @@ package ml import ( "context" + "os" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" ) +var h harness.Harness + // BenchmarkTensorflow runs workloads from a TensorFlow tutorial. // See: https://github.com/aymericdamien/TensorFlow-Examples func BenchmarkTensorflow(b *testing.B) { @@ -44,12 +47,12 @@ func BenchmarkTensorflow(b *testing.B) { for name, workload := range workloads { b.Run(name, func(b *testing.B) { ctx := context.Background() - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() + container := machine.GetContainer(ctx, b) + defer container.CleanUp(ctx) if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches: %v. You probably need root.", err) } @@ -67,3 +70,8 @@ func BenchmarkTensorflow(b *testing.B) { } } + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD index 472b5c387..2741570f5 100644 --- a/test/benchmarks/network/BUILD +++ b/test/benchmarks/network/BUILD @@ -1,4 +1,5 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") +load("//test/benchmarks:defs.bzl", "benchmark_test") package(licenses = ["notice"]) @@ -7,7 +8,6 @@ go_library( testonly = 1, srcs = [ "network.go", - "static_server.go", ], deps = [ "//pkg/test/dockerutil", @@ -16,22 +16,74 @@ go_library( ], ) -go_test( - name = "network_test", - size = "large", +benchmark_test( + name = "iperf_test", + size = "enormous", srcs = [ - "httpd_test.go", "iperf_test.go", - "nginx_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//pkg/test/testutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "node_test", + size = "enormous", + srcs = [ "node_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "ruby_test", + size = "enormous", + srcs = [ "ruby_test.go", ], library = ":network", - tags = [ - # Requires docker and runsc to be configured before test runs. - "manual", - "local", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "nginx_test", + size = "enormous", + srcs = [ + "nginx_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", ], +) + +benchmark_test( + name = "httpd_test", + size = "enormous", + srcs = [ + "httpd_test.go", + ], + library = ":network", visibility = ["//:sandbox"], deps = [ "//pkg/test/dockerutil", diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go index 8d7d5f750..b07274662 100644 --- a/test/benchmarks/network/httpd_test.go +++ b/test/benchmarks/network/httpd_test.go @@ -14,13 +14,17 @@ package network import ( + "os" "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" + "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + // see Dockerfile '//images/benchmarks/httpd'. var httpdDocs = map[string]string{ "notfound": "notfound", @@ -43,6 +47,22 @@ func BenchmarkReverseHttpd(b *testing.B) { benchmarkHttpdDocSize(b, true /* reverse */) } +// BenchmarkContinuousHttpd runs specific benchmarks for continous jobs. +// The runtime under test is the server serving a runc client. +func BenchmarkContinuousHttpd(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkHttpdContinuous(b, threads, sizes, false /*reverse*/) +} + +// BenchmarkContinuousHttpdReverse runs specific benchmarks for continous jobs. +// The runtime under test is the client downloading from a runc server. +func BenchmarkContinuousHttpdReverse(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkHttpdContinuous(b, threads, sizes, true /*reverse*/) +} + // benchmarkHttpdDocSize iterates through all doc sizes, running subbenchmarks // for each size. func benchmarkHttpdDocSize(b *testing.B, reverse bool) { @@ -62,9 +82,51 @@ func benchmarkHttpdDocSize(b *testing.B, reverse bool) { if err != nil { b.Fatalf("Failed to parse parameters: %v", err) } + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } + b.Run(name, func(b *testing.B) { + hey := &tools.Hey{ + Requests: requests, + Concurrency: c, + Doc: filename, + } + runHttpd(b, hey, reverse) + }) + } + } +} + +// benchmarkHttpdContinuous iterates through given sizes and concurrencies. +func benchmarkHttpdContinuous(b *testing.B, concurrency []int, sizes []string, reverse bool) { + for _, size := range sizes { + filename := httpdDocs[size] + for _, c := range concurrency { + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + + threads := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + + name, err := tools.ParametersToName(fsize, threads) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: c * b.N, + Requests: requests, Concurrency: c, Doc: filename, } @@ -91,5 +153,10 @@ func runHttpd(b *testing.B, hey *tools.Hey, reverse bool) { }, } httpdCmd := []string{"sh", "-c", "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"} - runStaticServer(b, httpdRunOpts, httpdCmd, port, hey, reverse) + runStaticServer(b, h, httpdRunOpts, httpdCmd, port, hey, reverse) +} + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go index b8ab7dfb8..9d64db943 100644 --- a/test/benchmarks/network/iperf_test.go +++ b/test/benchmarks/network/iperf_test.go @@ -15,6 +15,7 @@ package network import ( "context" + "os" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" @@ -23,9 +24,11 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + func BenchmarkIperf(b *testing.B) { iperf := tools.Iperf{ - Time: 10, // time in seconds to run client. + Time: b.N, // time in seconds to run client. } clientMachine, err := h.GetMachine() @@ -97,17 +100,19 @@ func BenchmarkIperf(b *testing.B) { // Restart the server profiles. If the server isn't being profiled // this does nothing. server.RestartProfiles() - for i := 0; i < b.N; i++ { - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/iperf", - }, iperf.MakeCmd(ip, servingPort)...) - if err != nil { - b.Fatalf("failed to run client: %v", err) - } - b.StopTimer() - iperf.Report(b, out) - b.StartTimer() + out, err := client.Run(ctx, dockerutil.RunOpts{ + Image: "benchmarks/iperf", + }, iperf.MakeCmd(ip, servingPort)...) + if err != nil { + b.Fatalf("failed to run client: %v", err) } + b.StopTimer() + iperf.Report(b, out) }) } } + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/network/network.go b/test/benchmarks/network/network.go index ce17ddb94..b18bc2b3c 100644 --- a/test/benchmarks/network/network.go +++ b/test/benchmarks/network/network.go @@ -16,16 +16,73 @@ package network import ( - "os" + "context" "testing" + "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" + "gvisor.dev/gvisor/test/benchmarks/tools" ) -var h harness.Harness +// runStaticServer runs static serving workloads (httpd, nginx). +func runStaticServer(b *testing.B, h harness.Harness, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) { + ctx := context.Background() -// TestMain is the main method for package network. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) + // Get two machines: a client and server. + clientMachine, err := h.GetMachine() + if err != nil { + b.Fatalf("failed to get machine: %v", err) + } + defer clientMachine.CleanUp() + + serverMachine, err := h.GetMachine() + if err != nil { + b.Fatalf("failed to get machine: %v", err) + } + defer serverMachine.CleanUp() + + // Make the containers. 'reverse=true' specifies that the client should use the + // runtime under test. + var client, server *dockerutil.Container + if reverse { + client = clientMachine.GetContainer(ctx, b) + server = serverMachine.GetNativeContainer(ctx, b) + } else { + client = clientMachine.GetNativeContainer(ctx, b) + server = serverMachine.GetContainer(ctx, b) + } + defer client.CleanUp(ctx) + defer server.CleanUp(ctx) + + // Start the server. + if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil { + b.Fatalf("failed to start server: %v", err) + } + + // Get its IP. + ip, err := serverMachine.IPAddress() + if err != nil { + b.Fatalf("failed to find server ip: %v", err) + } + + // Get the published port. + servingPort, err := server.FindPort(ctx, port) + if err != nil { + b.Fatalf("failed to find server port %d: %v", port, err) + } + + // Make sure the server is serving. + harness.WaitUntilServing(ctx, clientMachine, ip, servingPort) + b.ResetTimer() + server.RestartProfiles() + out, err := client.Run(ctx, dockerutil.RunOpts{ + Image: "benchmarks/hey", + }, hey.MakeCmd(ip, servingPort)...) + if err != nil { + b.Fatalf("run failed with: %v", err) + } + + b.StopTimer() + hey.Report(b, out) + b.StartTimer() } diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go index 08565d0b2..87449612a 100644 --- a/test/benchmarks/network/nginx_test.go +++ b/test/benchmarks/network/nginx_test.go @@ -14,13 +14,17 @@ package network import ( + "os" "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" + "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + // see Dockerfile '//images/benchmarks/nginx'. var nginxDocs = map[string]string{ "notfound": "notfound", @@ -44,6 +48,22 @@ func BenchmarkReverseNginxDocSize(b *testing.B) { benchmarkNginxDocSize(b, true /* reverse */, true /* tmpfs */) } +// BenchmarkContinuousNginx runs specific benchmarks for continous jobs. +// The runtime under test is the sever serving a runc client. +func BenchmarkContinuousNginx(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkNginxContinuous(b, threads, sizes, false /*reverse*/) +} + +// BenchmarkContinuousNginxReverse runs specific benchmarks for continous jobs. +// The runtime under test is the client downloading from a runc server. +func BenchmarkContinuousNginxReverse(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkNginxContinuous(b, threads, sizes, true /*reverse*/) +} + // benchmarkNginxDocSize iterates through all doc sizes, running subbenchmarks // for each size. func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { @@ -72,9 +92,14 @@ func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { b.Fatalf("Failed to parse parameters: %v", err) } + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: c * b.N, + Requests: requests, Concurrency: c, Doc: filename, } @@ -84,6 +109,47 @@ func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { } } +// benchmarkNginxContinuous iterates through given sizes and concurrencies on a tmpfs mount. +func benchmarkNginxContinuous(b *testing.B, concurrency []int, sizes []string, reverse bool) { + for _, size := range sizes { + filename := nginxDocs[size] + for _, c := range concurrency { + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + + threads := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + + fs := tools.Parameter{ + Name: "filesystem", + Value: "tmpfs", + } + + name, err := tools.ParametersToName(fsize, threads, fs) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } + b.Run(name, func(b *testing.B) { + hey := &tools.Hey{ + Requests: requests, + Concurrency: c, + Doc: filename, + } + runNginx(b, hey, reverse, true /*tmpfs*/) + }) + } + } +} + // runNginx configures the static serving methods to run httpd. func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) { // nginx runs on port 80. @@ -99,5 +165,10 @@ func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) { } // Command copies nginxDocs to tmpfs serving directory and runs nginx. - runStaticServer(b, nginxRunOpts, nginxCmd, port, hey, reverse) + runStaticServer(b, h, nginxRunOpts, nginxCmd, port, hey, reverse) +} + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go index 254538899..3e837a9e4 100644 --- a/test/benchmarks/network/node_test.go +++ b/test/benchmarks/network/node_test.go @@ -15,6 +15,7 @@ package network import ( "context" + "os" "strconv" "testing" "time" @@ -24,6 +25,8 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + // BenchmarkNode runs requests using 'hey' against a Node server run on // 'runtime'. The server responds to requests by grabbing some data in a // redis instance and returns the data in its reponse. The test loops through @@ -39,9 +42,14 @@ func BenchmarkNode(b *testing.B) { if err != nil { b.Fatalf("Failed to parse parameters: %v", err) } + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: b.N * c, // Requests b.N requests per thread. + Requests: requests, Concurrency: c, } runNode(b, hey) @@ -131,5 +139,9 @@ func runNode(b *testing.B, hey *tools.Hey) { // Stop the timer to parse the data and report stats. b.StopTimer() hey.Report(b, out) - b.StartTimer() +} + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go index 0174ff3f3..c89672873 100644 --- a/test/benchmarks/network/ruby_test.go +++ b/test/benchmarks/network/ruby_test.go @@ -16,6 +16,7 @@ package network import ( "context" "fmt" + "os" "strconv" "testing" "time" @@ -25,6 +26,8 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) +var h harness.Harness + // BenchmarkRuby runs requests using 'hey' against a ruby application server. // On start, ruby app generates some random data and pushes it to a redis // instance. On a request, the app grabs for random entries from the redis @@ -40,9 +43,14 @@ func BenchmarkRuby(b *testing.B) { if err != nil { b.Fatalf("Failed to parse parameters: %v", err) } + requests := b.N + if requests < c { + b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c) + requests = c + } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: b.N * c, // b.N requests per thread. + Requests: requests, Concurrency: c, } runRuby(b, hey) @@ -52,7 +60,6 @@ func BenchmarkRuby(b *testing.B) { // runRuby runs the test for a given # of requests and concurrency. func runRuby(b *testing.B, hey *tools.Hey) { - b.Helper() // The machine to hold Redis and the Ruby Server. serverMachine, err := h.GetMachine() if err != nil { @@ -141,3 +148,8 @@ func runRuby(b *testing.B, hey *tools.Hey) { hey.Report(b, out) b.StartTimer() } + +func TestMain(m *testing.M) { + h.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/network/static_server.go b/test/benchmarks/network/static_server.go deleted file mode 100644 index e747a1395..000000000 --- a/test/benchmarks/network/static_server.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "context" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// runStaticServer runs static serving workloads (httpd, nginx). -func runStaticServer(b *testing.B, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) { - ctx := context.Background() - - // Get two machines: a client and server. - clientMachine, err := h.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer clientMachine.CleanUp() - - serverMachine, err := h.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer serverMachine.CleanUp() - - // Make the containers. 'reverse=true' specifies that the client should use the - // runtime under test. - var client, server *dockerutil.Container - if reverse { - client = clientMachine.GetContainer(ctx, b) - server = serverMachine.GetNativeContainer(ctx, b) - } else { - client = clientMachine.GetNativeContainer(ctx, b) - server = serverMachine.GetContainer(ctx, b) - } - defer client.CleanUp(ctx) - defer server.CleanUp(ctx) - - // Start the server. - if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil { - b.Fatalf("failed to start server: %v", err) - } - - // Get its IP. - ip, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to find server ip: %v", err) - } - - // Get the published port. - servingPort, err := server.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to find server port %d: %v", port, err) - } - - // Make sure the server is serving. - harness.WaitUntilServing(ctx, clientMachine, ip, servingPort) - b.ResetTimer() - server.RestartProfiles() - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/hey", - }, hey.MakeCmd(ip, servingPort)...) - if err != nil { - b.Fatalf("run failed with: %v", err) - } - - b.StopTimer() - hey.Report(b, out) - b.StartTimer() -} diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go index 5c4e7125b..891d32704 100644 --- a/test/benchmarks/tools/iperf.go +++ b/test/benchmarks/tools/iperf.go @@ -31,7 +31,7 @@ type Iperf struct { // MakeCmd returns a iperf client command. func (i *Iperf) MakeCmd(ip net.IP, port int) []string { // iperf report in Kb realtime - return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d -c %s -p %d", i.Time, ip, port), " ") + return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d --client %s --port %d", i.Time, ip, port), " ") } // Report parses output from iperf client and reports metrics. diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go index e35886437..a42e3456e 100644 --- a/test/benchmarks/tools/redis.go +++ b/test/benchmarks/tools/redis.go @@ -29,17 +29,17 @@ type Redis struct { } // MakeCmd returns a redis-benchmark client command. -func (r *Redis) MakeCmd(ip net.IP, port int) []string { +func (r *Redis) MakeCmd(ip net.IP, port, requests int) []string { // There is no -t PING_BULK for redis-benchmark, so adjust the command in that case. // Note that "ping" will run both PING_INLINE and PING_BULK. if r.Operation == "PING_BULK" { return strings.Split( - fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d", ip, port), " ") + fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d -n %d", ip, port, requests), " ") } // runs redis-benchmark -t operation for 100K requests against server. return strings.Split( - fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d", r.Operation, ip, port), " ") + fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d -n %d", r.Operation, ip, port, requests), " ") } // Report parses output from redis-benchmark client and reports metrics. diff --git a/test/cmd/test_app/fds.go b/test/cmd/test_app/fds.go index a7658eefd..d4354f0d3 100644 --- a/test/cmd/test_app/fds.go +++ b/test/cmd/test_app/fds.go @@ -16,6 +16,7 @@ package main import ( "context" + "io" "io/ioutil" "log" "os" @@ -168,8 +169,8 @@ func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...int file := os.NewFile(uintptr(fd), "received file") defer file.Close() - if _, err := file.Seek(0, os.SEEK_SET); err != nil { - log.Fatalf("Seek(0, 0) failed: %v", err) + if _, err := file.Seek(0, io.SeekStart); err != nil { + log.Fatalf("Error from seek(0, 0): %v", err) } got, err := ioutil.ReadAll(file) diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index 8425abecb..03bdfa889 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -494,6 +494,55 @@ func TestLink(t *testing.T) { } } +// This test ensures we can run ping without errors. +func TestPing4Loopback(t *testing.T) { + if testutil.IsRunningWithHostNet() { + // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable + // this test. + t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + if got, err := d.Run(ctx, dockerutil.RunOpts{ + Image: "basic/ping4test", + }, "/root/ping4.sh"); err != nil { + t.Fatalf("docker run failed: %s", err) + } else if got != "" { + t.Errorf("test failed:\n%s", got) + } +} + +// This test ensures we can enable ipv6 on loopback and run ping6 without +// errors. +func TestPing6Loopback(t *testing.T) { + if testutil.IsRunningWithHostNet() { + // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable + // this test. + t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + if got, err := d.Run(ctx, dockerutil.RunOpts{ + Image: "basic/ping6test", + // The CAP_NET_ADMIN capability is required to use the `ip` utility, which + // we use to enable ipv6 on loopback. + // + // By default, ipv6 loopback is not enabled by runsc, because docker does + // not assign an ipv6 address to the test container. + CapAdd: []string{"NET_ADMIN"}, + }, "/root/ping6.sh"); err != nil { + t.Fatalf("docker run failed: %s", err) + } else if got != "" { + t.Errorf("test failed:\n%s", got) + } +} + func TestMain(m *testing.M) { dockerutil.EnsureSupportedDockerVersion() flag.Parse() diff --git a/test/fuse/BUILD b/test/fuse/BUILD index 8e31fdd41..74500ec84 100644 --- a/test/fuse/BUILD +++ b/test/fuse/BUILD @@ -71,3 +71,8 @@ syscall_test( fuse = "True", test = "//test/fuse/linux:setstat_test", ) + +syscall_test( + fuse = "True", + test = "//test/fuse/linux:mount_test", +) diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD index 7673252ec..d1fb178e8 100644 --- a/test/fuse/linux/BUILD +++ b/test/fuse/linux/BUILD @@ -228,3 +228,15 @@ cc_binary( "//test/util:test_util", ], ) + +cc_binary( + name = "mount_test", + testonly = 1, + srcs = ["mount_test.cc"], + deps = [ + gtest, + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + ], +) diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc new file mode 100644 index 000000000..a5c2fbb01 --- /dev/null +++ b/test/fuse/linux/mount_test.cc @@ -0,0 +1,41 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <errno.h> +#include <fcntl.h> +#include <sys/mount.h> + +#include "gtest/gtest.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +TEST(FuseMount, FDNotParsable) { + int devfd; + EXPECT_THAT(devfd = open("/dev/fuse", O_RDWR), SyscallSucceeds()); + std::string mount_opts = "fd=thiscantbeparsed"; + TempPath mount_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + EXPECT_THAT(mount("fuse", mount_dir.path().c_str(), "fuse", + MS_NODEV | MS_NOSUID, mount_opts.c_str()), + SyscallFailsWithErrno(EINVAL)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go index d3e5efd4f..f4af45e96 100644 --- a/test/iptables/filter_output.go +++ b/test/iptables/filter_output.go @@ -248,7 +248,7 @@ func (FilterOutputOwnerFail) Name() string { // ContainerAction implements TestCase.ContainerAction. func (FilterOutputOwnerFail) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil { - return fmt.Errorf("Invalid argument") + return fmt.Errorf("invalid argument") } return nil diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go index 834f7615f..4733146c0 100644 --- a/test/iptables/iptables_test.go +++ b/test/iptables/iptables_test.go @@ -89,6 +89,10 @@ func iptablesTest(t *testing.T, test TestCase, ipv6 bool) { // Get the container IP. ip, err := d.FindIP(ctx, ipv6) if err != nil { + // If ipv6 is not configured, don't fail. + if ipv6 && err == dockerutil.ErrNoIP { + t.Skipf("No ipv6 address is available.") + } t.Fatalf("failed to get container IP: %v", err) } diff --git a/test/packetdrill/BUILD b/test/packetdrill/BUILD index 49642f282..5d95516ee 100644 --- a/test/packetdrill/BUILD +++ b/test/packetdrill/BUILD @@ -38,6 +38,15 @@ packetdrill_test( scripts = ["tcp_defer_accept_timeout.pkt"], ) +test_suite( + name = "all_tests", + tags = [ + "manual", + "packetdrill", + ], + tests = existing_rules(), +) + bzl_library( name = "defs_bzl", srcs = ["defs.bzl"], diff --git a/test/packetdrill/defs.bzl b/test/packetdrill/defs.bzl index fc28ce9ba..a6cbcc376 100644 --- a/test/packetdrill/defs.bzl +++ b/test/packetdrill/defs.bzl @@ -15,7 +15,7 @@ def _packetdrill_test_impl(ctx): # Make sure that everything is readable here. "find . -type f -exec chmod a+rx {} \\;", "find . -type d -exec chmod a+rx {} \\;", - "%s %s --init_script %s $@ -- %s\n" % ( + "%s %s --init_script %s \"$@\" -- %s\n" % ( test_runner.short_path, " ".join(ctx.attr.flags), ctx.files._init_script[0].short_path, @@ -80,9 +80,7 @@ def packetdrill_netstack_test(name, **kwargs): kwargs["tags"] = PACKETDRILL_TAGS _packetdrill_test( name = name, - # This is the default runtime unless - # "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value. - flags = ["--dut_platform", "netstack", "--runtime", "runsc-d"], + flags = ["--dut_platform", "netstack"], **kwargs ) diff --git a/test/packetdrill/packetdrill_test.sh b/test/packetdrill/packetdrill_test.sh index 922547d65..d25cad83a 100755 --- a/test/packetdrill/packetdrill_test.sh +++ b/test/packetdrill/packetdrill_test.sh @@ -29,7 +29,7 @@ function failure() { } trap 'failure ${LINENO} "$BASH_COMMAND"' ERR -declare -r LONGOPTS="dut_platform:,init_script:,runtime:" +declare -r LONGOPTS="dut_platform:,init_script:,runtime:,partition:,total_partitions:" # Don't use declare below so that the error from getopt will end the script. PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@") @@ -48,12 +48,17 @@ while true; do shift 2 ;; --runtime) - # Not readonly because there might be multiple --runtime arguments and we - # want to use just the last one. Only used if --dut_platform is - # "netstack". declare RUNTIME="$2" shift 2 ;; + --partition) + # Ignored. + shift 2 + ;; + --total_partitions) + # Ignored. + shift 2 + ;; --) shift break diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD index 605dd4972..888c44343 100644 --- a/test/packetimpact/runner/BUILD +++ b/test/packetimpact/runner/BUILD @@ -32,6 +32,7 @@ go_library( deps = [ "//pkg/test/dockerutil", "//test/packetimpact/netdevs", + "//test/packetimpact/testbench", "@com_github_docker_docker//api/types/mount:go_default_library", ], ) diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 1038e3c8d..c6c95546a 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -12,10 +12,11 @@ def _packetimpact_test_impl(ctx): # current user, and no other users will be mapped in that namespace. # Make sure that everything is readable here. "find . -type f -or -type d -exec chmod a+rx {} \\;", - "%s %s --testbench_binary %s $@\n" % ( + "%s %s --testbench_binary %s --num_duts %d $@\n" % ( test_runner.short_path, " ".join(ctx.attr.flags), ctx.files.testbench_binary[0].short_path, + ctx.attr.num_duts, ), ]) ctx.actions.write(bench, bench_content, is_executable = True) @@ -51,6 +52,10 @@ _packetimpact_test = rule( mandatory = False, default = [], ), + "num_duts": attr.int( + mandatory = False, + default = 1, + ), }, test = True, implementation = _packetimpact_test_impl, @@ -110,24 +115,27 @@ def packetimpact_netstack_test( **kwargs ) -def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False): +def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1): """Add packetimpact tests written in go. Args: name: name of the test expect_native_failure: the test must fail natively expect_netstack_failure: the test must fail for Netstack + num_duts: how many DUTs are needed for the test """ testbench_binary = name + "_test" packetimpact_native_test( name = name, expect_failure = expect_native_failure, testbench_binary = testbench_binary, + num_duts = num_duts, ) packetimpact_netstack_test( name = name, expect_failure = expect_netstack_failure, testbench_binary = testbench_binary, + num_duts = num_duts, ) def packetimpact_testbench(name, size = "small", pure = True, **kwargs): @@ -153,7 +161,7 @@ def packetimpact_testbench(name, size = "small", pure = True, **kwargs): PacketimpactTestInfo = provider( doc = "Provide information for packetimpact tests", - fields = ["name", "expect_netstack_failure"], + fields = ["name", "expect_netstack_failure", "num_duts"], ) ALL_TESTS = [ @@ -216,6 +224,9 @@ ALL_TESTS = [ name = "tcp_user_timeout", ), PacketimpactTestInfo( + name = "tcp_zero_receive_window", + ), + PacketimpactTestInfo( name = "tcp_queue_receive_in_syn_sent", ), PacketimpactTestInfo( @@ -243,13 +254,9 @@ ALL_TESTS = [ ), PacketimpactTestInfo( name = "icmpv6_param_problem", - # TODO(b/153485026): Fix netstack then remove the line below. - expect_netstack_failure = True, ), PacketimpactTestInfo( name = "ipv6_unknown_options_action", - # TODO(b/159928940): Fix netstack then remove the line below. - expect_netstack_failure = True, ), PacketimpactTestInfo( name = "ipv4_fragment_reassembly", @@ -259,6 +266,7 @@ ALL_TESTS = [ ), PacketimpactTestInfo( name = "ipv6_fragment_icmp_error", + num_duts = 3, ), PacketimpactTestInfo( name = "udp_send_recv_dgram", diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 59bb68eb1..3e26c73cb 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -17,6 +17,7 @@ package runner import ( "context" + "encoding/json" "flag" "fmt" "io/ioutil" @@ -34,6 +35,7 @@ import ( "github.com/docker/docker/api/types/mount" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/packetimpact/netdevs" + "gvisor.dev/gvisor/test/packetimpact/testbench" ) // stringList implements flag.Value. @@ -56,9 +58,10 @@ var ( tshark = false extraTestArgs = stringList{} expectFailure = false + numDUTs = 1 - // DutAddr is the IP addres for DUT. - DutAddr = net.IPv4(0, 0, 0, 10) + // DUTAddr is the IP addres for DUT. + DUTAddr = net.IPv4(0, 0, 0, 10) testbenchAddr = net.IPv4(0, 0, 0, 20) ) @@ -71,10 +74,15 @@ func RegisterFlags(fs *flag.FlagSet) { fs.BoolVar(&tshark, "tshark", false, "use more verbose tshark in logs instead of tcpdump") fs.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench") fs.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run") + fs.IntVar(&numDUTs, "num_duts", numDUTs, "the number of duts to create") } -// CtrlPort is the port that posix_server listens on. -const CtrlPort = "40000" +const ( + // CtrlPort is the port that posix_server listens on. + CtrlPort uint16 = 40000 + // testOutputDir is the directory in each container that holds test output. + testOutputDir = "/tmp/testoutput" +) // logger implements testutil.Logger. // @@ -95,16 +103,21 @@ func (l logger) Logf(format string, args ...interface{}) { } } -// TestWithDUT runs a packetimpact test with the given information. -func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT, containerAddr net.IP) { - if testbenchBinary == "" { - t.Fatal("--testbench_binary is missing") - } - dockerutil.EnsureSupportedDockerVersion() +// dutInfo encapsulates all the essential information to set up testbench +// container. +type dutInfo struct { + dut DUT + ctrlNet, testNet *dockerutil.Network + netInfo *testbench.DUTTestNet +} - // Create the networks needed for the test. One control network is needed for - // the gRPC control packets and one test network on which to transmit the test - // packets. +// setUpDUT will set up one DUT and return information for setting up the +// container for testbench. +func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerutil.Container) DUT) (dutInfo, error) { + // Create the networks needed for the test. One control network is needed + // for the gRPC control packets and one test network on which to transmit + // the test packets. + var info dutInfo ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet")) testNet := dockerutil.NewNetwork(ctx, logger("testNet")) for _, dn := range []*dockerutil.Network{ctrlNet, testNet} { @@ -113,8 +126,8 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co t.Log("creating docker network:", err) const wait = 100 * time.Millisecond t.Logf("sleeping %s and will try creating docker network again", wait) - // This can fail if another docker network claimed the same IP so we'll - // just try again. + // This can fail if another docker network claimed the same IP so we + // will just try again. time.Sleep(wait) continue } @@ -128,115 +141,204 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co }) // Sanity check. if inspect, err := dn.Inspect(ctx); err != nil { - t.Fatalf("failed to inspect network %s: %v", dn.Name, err) + return dutInfo{}, fmt.Errorf("failed to inspect network %s: %w", dn.Name, err) } else if inspect.Name != dn.Name { - t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name) + return dutInfo{}, fmt.Errorf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name) } } - - tmpDir, err := ioutil.TempDir("", "container-output") - if err != nil { - t.Fatal("creating temp dir:", err) - } - t.Cleanup(func() { - if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil { - t.Errorf("unable to copy container output files: %s", err) - } - if err := os.RemoveAll(tmpDir); err != nil { - t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err) - } - }) - - const testOutputDir = "/tmp/testoutput" + info.ctrlNet = ctrlNet + info.testNet = testNet // Create the Docker container for the DUT. - var dut *dockerutil.Container + var dut DUT if native { - dut = dockerutil.MakeNativeContainer(ctx, logger("dut")) + dut = mkDevice(dockerutil.MakeNativeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) } else { - dut = dockerutil.MakeContainer(ctx, logger("dut")) + dut = mkDevice(dockerutil.MakeContainer(ctx, logger(fmt.Sprintf("dut-%d", id)))) } - t.Cleanup(func() { - dut.CleanUp(ctx) - }) + info.dut = dut runOpts := dockerutil.RunOpts{ Image: "packetimpact", CapAdd: []string{"NET_ADMIN"}, - Mounts: []mount.Mount{{ - Type: mount.TypeBind, - Source: tmpDir, - Target: testOutputDir, - ReadOnly: false, - }}, + } + if _, err := MountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil { + return dutInfo{}, err } - device := mkDevice(dut) - remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev := device.Prepare(ctx, t, runOpts, ctrlNet, testNet, containerAddr) + ipv4PrefixLength, _ := testNet.Subnet.Mask.Size() + remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := dut.Prepare(ctx, t, runOpts, ctrlNet, testNet) + if err != nil { + return dutInfo{}, err + } + info.netInfo = &testbench.DUTTestNet{ + RemoteMAC: remoteMAC, + RemoteIPv4: AddressInSubnet(DUTAddr, *testNet.Subnet), + RemoteIPv6: remoteIPv6, + RemoteDevID: dutDeviceID, + RemoteDevName: dutTestNetDev, + LocalIPv4: AddressInSubnet(testbenchAddr, *testNet.Subnet), + IPv4PrefixLength: ipv4PrefixLength, + POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet), + POSIXServerPort: CtrlPort, + } + return info, nil +} - // Create the Docker container for the testbench. - testbench := dockerutil.MakeNativeContainer(ctx, logger("testbench")) +// TestWithDUT runs a packetimpact test with the given information. +func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT) { + if testbenchBinary == "" { + t.Fatal("--testbench_binary is missing") + } + dockerutil.EnsureSupportedDockerVersion() - tbb := path.Base(testbenchBinary) - containerTestbenchBinary := filepath.Join("/packetimpact", tbb) - testbench.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb)) - - // snifferNetDev is a network device on the test orchestrator that we will - // run sniffer (tcpdump or tshark) on and inject traffic to, not to be - // confused with the device on the DUT. - const snifferNetDev = "eth2" - // Run tcpdump in the test bench unbuffered, without DNS resolution, just on - // the interface with the test packets. - snifferArgs := []string{ - "tcpdump", - "-S", "-vvv", "-U", "-n", - "-i", snifferNetDev, - "-w", testOutputDir + "/dump.pcap", + dutInfoChan := make(chan dutInfo, numDUTs) + errChan := make(chan error, numDUTs) + var dockerNetworks []*dockerutil.Network + var dutTestNets []*testbench.DUTTestNet + var duts []DUT + + setUpCtx, cancelSetup := context.WithCancel(ctx) + t.Cleanup(cancelSetup) + for i := 0; i < numDUTs; i++ { + go func(i int) { + info, err := setUpDUT(setUpCtx, t, i, mkDevice) + if err != nil { + errChan <- err + } else { + dutInfoChan <- info + } + }(i) } - snifferRegex := "tcpdump: listening.*\n" - if tshark { - // Run tshark in the test bench unbuffered, without DNS resolution, just on - // the interface with the test packets. - snifferArgs = []string{ - "tshark", "-V", "-l", "-n", "-i", snifferNetDev, - "-o", "tcp.check_checksum:TRUE", - "-o", "udp.check_checksum:TRUE", + for i := 0; i < numDUTs; i++ { + select { + case info := <-dutInfoChan: + dockerNetworks = append(dockerNetworks, info.ctrlNet, info.testNet) + dutTestNets = append(dutTestNets, info.netInfo) + duts = append(duts, info.dut) + case err := <-errChan: + t.Fatal(err) } - snifferRegex = "Capturing on.*\n" } + // Create the Docker container for the testbench. + testbenchContainer := dockerutil.MakeNativeContainer(ctx, logger("testbench")) + + runOpts := dockerutil.RunOpts{ + Image: "packetimpact", + CapAdd: []string{"NET_ADMIN"}, + } + if _, err := MountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil { + t.Fatal(err) + } + tbb := path.Base(testbenchBinary) + containerTestbenchBinary := filepath.Join("/packetimpact", tbb) + testbenchContainer.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb)) + if err := StartContainer( ctx, runOpts, - testbench, + testbenchContainer, testbenchAddr, - []*dockerutil.Network{ctrlNet, testNet}, - snifferArgs..., + dockerNetworks, + "tail", "-f", "/dev/null", ); err != nil { - t.Fatalf("failed to start docker container for testbench sniffer: %s", err) + t.Fatalf("cannot start testbench container: %s", err) } - // Kill so that it will flush output. - t.Cleanup(func() { - time.Sleep(1 * time.Second) - testbench.Exec(ctx, dockerutil.ExecOpts{}, "killall", snifferArgs[0]) - }) - if _, err := testbench.WaitForOutput(ctx, snifferRegex, 60*time.Second); err != nil { - t.Fatalf("sniffer on %s never listened: %s", dut.Name, err) + for i := range dutTestNets { + name, info, err := deviceByIP(ctx, testbenchContainer, dutTestNets[i].LocalIPv4) + if err != nil { + t.Fatalf("failed to get the device name associated with %s: %s", dutTestNets[i].LocalIPv4, err) + } + dutTestNets[i].LocalDevName = name + dutTestNets[i].LocalDevID = info.ID + dutTestNets[i].LocalMAC = info.MAC + localIPv6, err := getOrAssignIPv6Addr(ctx, testbenchContainer, name) + if err != nil { + t.Fatalf("failed to get IPV6 address on %s: %s", testbenchContainer.Name, err) + } + dutTestNets[i].LocalIPv6 = localIPv6 + } + dutTestNetsBytes, err := json.Marshal(dutTestNets) + if err != nil { + t.Fatalf("failed to marshal %v into json: %s", dutTestNets, err) } - // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it - // will respond with an RST. In most packetimpact tests, the SYN is sent - // by the raw socket and the kernel knows nothing about the connection, this - // behavior will break lots of TCP related packetimpact tests. To prevent - // this, we can install the following iptables rules. The raw socket that - // packetimpact tests use will still be able to see everything. - for _, bin := range []string{"iptables", "ip6tables"} { - if logs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", snifferNetDev, "-p", "tcp", "-j", "DROP"); err != nil { - t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbench.Name, err, logs) + baseSnifferArgs := []string{ + "tcpdump", + "-vvv", + "--absolute-tcp-sequence-numbers", + "--packet-buffered", + // Disable DNS resolution. + "-n", + // run tcpdump as root since the output directory is owned by root. From + // `man tcpdump`: + // + // -Z user + // --relinquish-privileges=user + // If tcpdump is running as root, after opening the capture device + // or input savefile, change the user ID to user and the group ID to + // the primary group of user. + // This behavior is enabled by default (-Z tcpdump), and can be + // disabled by -Z root. + "-Z", "root", + } + if tshark { + baseSnifferArgs = []string{ + "tshark", + "-V", + "-o", "tcp.check_checksum:TRUE", + "-o", "udp.check_checksum:TRUE", + // Disable buffering. + "-l", + // Disable DNS resolution. + "-n", + } + } + for _, n := range dutTestNets { + snifferArgs := append(baseSnifferArgs, "-i", n.LocalDevName) + if !tshark { + snifferArgs = append( + snifferArgs, + "-w", + filepath.Join(testOutputDir, fmt.Sprintf("%s.pcap", n.LocalDevName)), + ) + } + p, err := testbenchContainer.ExecProcess(ctx, dockerutil.ExecOpts{}, snifferArgs...) + if err != nil { + t.Fatalf("failed to start exec a sniffer on %s: %s", n.LocalDevName, err) + } + t.Cleanup(func() { + if snifferOut, err := p.Logs(); err != nil { + t.Errorf("sniffer logs failed: %s\n%s", err, snifferOut) + } else { + t.Logf("sniffer logs:\n%s", snifferOut) + } + }) + // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it + // will respond with an RST. In most packetimpact tests, the SYN is sent + // by the raw socket, the kernel knows nothing about the connection, this + // behavior will break lots of TCP related packetimpact tests. To prevent + // this, we can install the following iptables rules. The raw socket that + // packetimpact tests use will still be able to see everything. + for _, bin := range []string{"iptables", "ip6tables"} { + if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", n.LocalDevName, "-p", "tcp", "-j", "DROP"); err != nil { + t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbenchContainer.Name, err, logs) + } } } + t.Cleanup(func() { + // Wait 1 second before killing tcpdump to give it time to flush + // any packets. On linux tests killing it immediately can + // sometimes result in partial pcaps. + time.Sleep(1 * time.Second) + if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil { + t.Errorf("failed to kill all sniffers: %s, logs: %s", err, logs) + } + }) + // FIXME(b/156449515): Some piece of the system has a race. The old // bash script version had a sleep, so we have one too. The race should // be fixed and this sleep removed. @@ -248,31 +350,29 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co testArgs := []string{containerTestbenchBinary} testArgs = append(testArgs, extraTestArgs...) testArgs = append(testArgs, - "--posix_server_ip", AddressInSubnet(DutAddr, *ctrlNet.Subnet).String(), - "--posix_server_port", CtrlPort, - "--remote_ipv4", AddressInSubnet(DutAddr, *testNet.Subnet).String(), - "--local_ipv4", AddressInSubnet(testbenchAddr, *testNet.Subnet).String(), - "--remote_ipv6", remoteIPv6.String(), - "--remote_mac", remoteMAC.String(), - "--remote_interface_id", fmt.Sprintf("%d", dutDeviceID), - "--local_device", snifferNetDev, - "--remote_device", dutTestNetDev, fmt.Sprintf("--native=%t", native), + "--dut_test_nets_json", string(dutTestNetsBytes), ) - testbenchLogs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, testArgs...) + testbenchLogs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, testArgs...) if (err != nil) != expectFailure { var dutLogs string - if logs, err := device.Logs(ctx); err != nil { - dutLogs = fmt.Sprintf("failed to fetch DUT logs: %s", err) - } else { - dutLogs = logs + for i, dut := range duts { + logs, err := dut.Logs(ctx) + if err != nil { + logs = fmt.Sprintf("failed to fetch DUT logs: %s", err) + } + dutLogs = fmt.Sprintf(`%s====== Begin of DUT-%d Logs ====== + +%s + +====== End of DUT-%d Logs ====== + +`, dutLogs, i, logs, i) } t.Errorf(`test error: %v, expect failure: %t -%s - -====== Begin of Testbench Logs ====== +%s====== Begin of Testbench Logs ====== %s @@ -285,7 +385,9 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co type DUT interface { // Prepare prepares the dut, starts posix_server and returns the IPv6, MAC // address, the interface ID, and the interface name for the testNet on DUT. - Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network, containerAddr net.IP) (net.IP, net.HardwareAddr, uint32, string) + // The t parameter is supposed to be used for t.Cleanup. Don't use it for + // t.Fatal/FailNow functions. + Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) // Logs retrieves the logs from the dut. Logs(ctx context.Context) (string, error) } @@ -303,7 +405,7 @@ func NewDockerDUT(c *dockerutil.Container) DUT { } // Prepare implements DUT.Prepare. -func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network, containerAddr net.IP) (net.IP, net.HardwareAddr, uint32, string) { +func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) { const containerPosixServerBinary = "/packetimpact/posix_server" dut.c.CopyFiles(&runOpts, "/packetimpact", "test/packetimpact/dut/posix_server") @@ -311,45 +413,31 @@ func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockeru ctx, runOpts, dut.c, - containerAddr, + DUTAddr, []*dockerutil.Network{ctrlNet, testNet}, containerPosixServerBinary, "--ip=0.0.0.0", - "--port="+CtrlPort, + fmt.Sprintf("--port=%d", CtrlPort), ); err != nil { - t.Fatalf("failed to start docker container for DUT: %s", err) + return nil, nil, 0, "", fmt.Errorf("failed to start docker container for DUT: %w", err) } if _, err := dut.c.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil { - t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err) + return nil, nil, 0, "", fmt.Errorf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err) } - dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(containerAddr, *testNet.Subnet)) + dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DUTAddr, *testNet.Subnet)) if err != nil { - t.Fatal(err) + return nil, nil, 0, "", err } - remoteMAC := dutDeviceInfo.MAC - remoteIPv6 := dutDeviceInfo.IPv6Addr - // Netstack as DUT doesn't assign IPv6 addresses automatically so do it if - // needed. - if remoteIPv6 == nil { - if _, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(remoteMAC).String(), "scope", "link", "dev", dutTestDevice); err != nil { - t.Fatalf("unable to ip addr add on container %s: %s", dut.c.Name, err) - } - // Now try again, to make sure that it worked. - _, dutDeviceInfo, err = deviceByIP(ctx, dut.c, AddressInSubnet(containerAddr, *testNet.Subnet)) - if err != nil { - t.Fatal(err) - } - remoteIPv6 = dutDeviceInfo.IPv6Addr - if remoteIPv6 == nil { - t.Fatalf("unable to set IPv6 address on container %s", dut.c.Name) - } + remoteIPv6, err := getOrAssignIPv6Addr(ctx, dut.c, dutTestDevice) + if err != nil { + return nil, nil, 0, "", fmt.Errorf("failed to get IPv6 address on %s: %s", dut.c.Name, err) } const testNetDev = "eth2" - return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev + return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev, nil } // Logs implements DUT.Logs. @@ -358,11 +446,7 @@ func (dut *DockerDUT) Logs(ctx context.Context) (string, error) { if err != nil { return "", err } - return fmt.Sprintf(`====== Begin of DUT Logs ====== - -%s - -====== End of DUT Logs ======`, logs), nil + return logs, nil } // AddNetworks connects docker network with the container and assigns the specific IP. @@ -378,25 +462,35 @@ func AddNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, netw } // AddressInSubnet combines the subnet provided with the address and returns a -// new address. The return address bits come from the subnet where the mask is 1 -// and from the ip address where the mask is 0. +// new address. The return address bits come from the subnet where the mask is +// 1 and from the ip address where the mask is 0. func AddressInSubnet(addr net.IP, subnet net.IPNet) net.IP { - var octets []byte + var octets net.IP for i := 0; i < 4; i++ { octets = append(octets, (subnet.IP.To4()[i]&subnet.Mask[i])+(addr.To4()[i]&(^subnet.Mask[i]))) } - return net.IP(octets) + return octets } -// deviceByIP finds a deviceInfo and device name from an IP address. -func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) { +// devicesInfo will run "ip addr show" on the container and parse the output +// to a map[string]netdevs.DeviceInfo. +func devicesInfo(ctx context.Context, d *dockerutil.Container) (map[string]netdevs.DeviceInfo, error) { out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "show") if err != nil { - return "", netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w\n%s", d.Name, err, out) + return map[string]netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w\n%s", d.Name, err, out) } devs, err := netdevs.ParseDevices(out) if err != nil { - return "", netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out) + return map[string]netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out) + } + return devs, nil +} + +// deviceByIP finds a deviceInfo and device name from an IP address. +func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) { + devs, err := devicesInfo(ctx, d) + if err != nil { + return "", netdevs.DeviceInfo{}, err } testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs) if err != nil { @@ -405,6 +499,36 @@ func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string return testDevice, deviceInfo, nil } +// getOrAssignIPv6Addr will try to get the IPv6 address for the interface; if an +// address was not assigned, a link-local address based on MAC will be assigned +// to that interface. +func getOrAssignIPv6Addr(ctx context.Context, d *dockerutil.Container, iface string) (net.IP, error) { + devs, err := devicesInfo(ctx, d) + if err != nil { + return net.IP{}, err + } + info := devs[iface] + if info.IPv6Addr != nil { + return info.IPv6Addr, nil + } + if info.MAC == nil { + return nil, fmt.Errorf("unable to find MAC address of %s", iface) + } + if logs, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(info.MAC).String(), "scope", "link", "dev", iface); err != nil { + return net.IP{}, fmt.Errorf("unable to ip addr add on container %s: %w, logs: %s", d.Name, err, logs) + } + // Now try again, to make sure that it worked. + devs, err = devicesInfo(ctx, d) + if err != nil { + return net.IP{}, err + } + info = devs[iface] + if info.IPv6Addr == nil { + return net.IP{}, fmt.Errorf("unable to set IPv6 address on container %s", d.Name) + } + return info.IPv6Addr, nil +} + // createDockerNetwork makes a randomly-named network that will start with the // namePrefix. The network will be a random /24 subnet. func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error { @@ -440,3 +564,30 @@ func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerut } return nil } + +// MountTempDirectory creates a temporary directory on host with the template +// and then mounts it into the container under the name provided. The temporary +// directory name is returned. Content in that directory will be copied to +// TEST_UNDECLARED_OUTPUTS_DIR in cleanup phase. +func MountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) { + t.Helper() + tmpDir, err := ioutil.TempDir("", hostDirTemplate) + if err != nil { + return "", fmt.Errorf("failed to create a temp dir: %w", err) + } + t.Cleanup(func() { + if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil { + t.Errorf("unable to copy container output files: %s", err) + } + if err := os.RemoveAll(tmpDir); err != nil { + t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err) + } + }) + runOpts.Mounts = append(runOpts.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: tmpDir, + Target: containerDir, + ReadOnly: false, + }) + return tmpDir, nil +} diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go index c598bfc29..46334b7ab 100644 --- a/test/packetimpact/runner/packetimpact_test.go +++ b/test/packetimpact/runner/packetimpact_test.go @@ -28,5 +28,5 @@ func init() { } func TestOne(t *testing.T) { - runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT, runner.DutAddr) + runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT) } diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD index 5a0ee1367..983c2c030 100644 --- a/test/packetimpact/testbench/BUILD +++ b/test/packetimpact/testbench/BUILD @@ -21,7 +21,6 @@ go_library( "//pkg/tcpip/header", "//pkg/tcpip/seqnum", "//pkg/usermem", - "//test/packetimpact/netdevs", "//test/packetimpact/proto:posix_server_go_proto", "@com_github_google_go_cmp//cmp:go_default_library", "@com_github_google_go_cmp//cmp/cmpopts:go_default_library", diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index 919b4fd25..576577310 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -17,7 +17,6 @@ package testbench import ( "fmt" "math/rand" - "net" "testing" "time" @@ -42,7 +41,7 @@ func portFromSockaddr(sa unix.Sockaddr) (uint16, error) { // pickPort makes a new socket and returns the socket FD and port. The domain // should be AF_INET or AF_INET6. The caller must close the FD when done with // the port if there is no error. -func pickPort(domain, typ int) (fd int, port uint16, err error) { +func (n *DUTTestNet) pickPort(domain, typ int) (fd int, port uint16, err error) { fd, err = unix.Socket(domain, typ, 0) if err != nil { return -1, 0, fmt.Errorf("creating socket: %w", err) @@ -58,11 +57,11 @@ func pickPort(domain, typ int) (fd int, port uint16, err error) { switch domain { case unix.AF_INET: var sa4 unix.SockaddrInet4 - copy(sa4.Addr[:], net.ParseIP(LocalIPv4).To4()) + copy(sa4.Addr[:], n.LocalIPv4) sa = &sa4 case unix.AF_INET6: - sa6 := unix.SockaddrInet6{ZoneId: uint32(LocalInterfaceID)} - copy(sa6.Addr[:], net.ParseIP(LocalIPv6).To16()) + sa6 := unix.SockaddrInet6{ZoneId: n.LocalDevID} + copy(sa6.Addr[:], n.LocalIPv6) sa = &sa6 default: return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain) @@ -117,19 +116,12 @@ type etherState struct { var _ layerState = (*etherState)(nil) // newEtherState creates a new etherState. -func newEtherState(out, in Ether) (*etherState, error) { - lMAC, err := tcpip.ParseMACAddress(LocalMAC) - if err != nil { - return nil, fmt.Errorf("parsing local MAC: %q: %w", LocalMAC, err) - } - - rMAC, err := tcpip.ParseMACAddress(RemoteMAC) - if err != nil { - return nil, fmt.Errorf("parsing remote MAC: %q: %w", RemoteMAC, err) - } +func (n *DUTTestNet) newEtherState(out, in Ether) (*etherState, error) { + lmac := tcpip.LinkAddress(n.LocalMAC) + rmac := tcpip.LinkAddress(n.RemoteMAC) s := etherState{ - out: Ether{SrcAddr: &lMAC, DstAddr: &rMAC}, - in: Ether{SrcAddr: &rMAC, DstAddr: &lMAC}, + out: Ether{SrcAddr: &lmac, DstAddr: &rmac}, + in: Ether{SrcAddr: &rmac, DstAddr: &lmac}, } if err := s.out.merge(&out); err != nil { return nil, err @@ -169,9 +161,9 @@ type ipv4State struct { var _ layerState = (*ipv4State)(nil) // newIPv4State creates a new ipv4State. -func newIPv4State(out, in IPv4) (*ipv4State, error) { - lIP := tcpip.Address(net.ParseIP(LocalIPv4).To4()) - rIP := tcpip.Address(net.ParseIP(RemoteIPv4).To4()) +func (n *DUTTestNet) newIPv4State(out, in IPv4) (*ipv4State, error) { + lIP := tcpip.Address(n.LocalIPv4) + rIP := tcpip.Address(n.RemoteIPv4) s := ipv4State{ out: IPv4{SrcAddr: &lIP, DstAddr: &rIP}, in: IPv4{SrcAddr: &rIP, DstAddr: &lIP}, @@ -214,9 +206,9 @@ type ipv6State struct { var _ layerState = (*ipv6State)(nil) // newIPv6State creates a new ipv6State. -func newIPv6State(out, in IPv6) (*ipv6State, error) { - lIP := tcpip.Address(net.ParseIP(LocalIPv6).To16()) - rIP := tcpip.Address(net.ParseIP(RemoteIPv6).To16()) +func (n *DUTTestNet) newIPv6State(out, in IPv6) (*ipv6State, error) { + lIP := tcpip.Address(n.LocalIPv6) + rIP := tcpip.Address(n.RemoteIPv6) s := ipv6State{ out: IPv6{SrcAddr: &lIP, DstAddr: &rIP}, in: IPv6{SrcAddr: &rIP, DstAddr: &lIP}, @@ -272,8 +264,8 @@ func SeqNumValue(v seqnum.Value) *seqnum.Value { } // newTCPState creates a new TCPState. -func newTCPState(domain int, out, in TCP) (*tcpState, error) { - portPickerFD, localPort, err := pickPort(domain, unix.SOCK_STREAM) +func (n *DUTTestNet) newTCPState(domain int, out, in TCP) (*tcpState, error) { + portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_STREAM) if err != nil { return nil, err } @@ -314,11 +306,11 @@ func (s *tcpState) incoming(received Layer) Layer { if s.remoteSeqNum != nil { newIn.SeqNum = Uint32(uint32(*s.remoteSeqNum)) } - if s.localSeqNum != nil && (*tcpReceived.Flags&header.TCPFlagAck) != 0 { + if seq, flags := s.localSeqNum, tcpReceived.Flags; seq != nil && flags != nil && *flags&header.TCPFlagAck != 0 { // The caller didn't specify an AckNum so we'll expect the calculated one, // but only if the ACK flag is set because the AckNum is not valid in a // header if ACK is not set. - newIn.AckNum = Uint32(uint32(*s.localSeqNum)) + newIn.AckNum = Uint32(uint32(*seq)) } return &newIn } @@ -376,8 +368,8 @@ type udpState struct { var _ layerState = (*udpState)(nil) // newUDPState creates a new udpState. -func newUDPState(domain int, out, in UDP) (*udpState, error) { - portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM) +func (n *DUTTestNet) newUDPState(domain int, out, in UDP) (*udpState, error) { + portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_DGRAM) if err != nil { return nil, fmt.Errorf("picking port: %w", err) } @@ -606,14 +598,14 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du var errs error for { var gotLayers Layers - if timeout = time.Until(deadline); timeout > 0 { + if timeout := time.Until(deadline); timeout > 0 { gotLayers = conn.recvFrame(t, timeout) } if gotLayers == nil { if errs == nil { - return nil, fmt.Errorf("got no frames matching %v during %s", layers, timeout) + return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout) } - return nil, fmt.Errorf("got frames %w want %v during %s", errs, layers, timeout) + return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout) } if conn.match(layers, gotLayers) { for i, s := range conn.layerStates { @@ -623,7 +615,12 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du } return gotLayers, nil } - errs = multierr.Combine(errs, &layersError{got: gotLayers, want: conn.incoming(gotLayers)}) + want := conn.incoming(layers) + if err := want.merge(layers); err != nil { + errs = multierr.Combine(errs, err) + } else { + errs = multierr.Combine(errs, &layersError{got: gotLayers, want: want}) + } } } @@ -639,26 +636,26 @@ func (conn *Connection) Drain(t *testing.T) { type TCPIPv4 Connection // NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults. -func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 { +func (n *DUTTestNet) NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 { t.Helper() - etherState, err := newEtherState(Ether{}, Ether{}) + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make etherState: %s", err) } - ipv4State, err := newIPv4State(IPv4{}, IPv4{}) + ipv4State, err := n.newIPv4State(IPv4{}, IPv4{}) if err != nil { t.Fatalf("can't make ipv4State: %s", err) } - tcpState, err := newTCPState(unix.AF_INET, outgoingTCP, incomingTCP) + tcpState, err := n.newTCPState(unix.AF_INET, outgoingTCP, incomingTCP) if err != nil { t.Fatalf("can't make tcpState: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } @@ -841,23 +838,23 @@ func (conn *TCPIPv4) Drain(t *testing.T) { type IPv4Conn Connection // NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults. -func NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn { +func (n *DUTTestNet) NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn { t.Helper() - etherState, err := newEtherState(Ether{}, Ether{}) + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make EtherState: %s", err) } - ipv4State, err := newIPv4State(outgoingIPv4, incomingIPv4) + ipv4State, err := n.newIPv4State(outgoingIPv4, incomingIPv4) if err != nil { t.Fatalf("can't make IPv4State: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } @@ -896,23 +893,23 @@ func (c *IPv4Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration type IPv6Conn Connection // NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults. -func NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn { +func (n *DUTTestNet) NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn { t.Helper() - etherState, err := newEtherState(Ether{}, Ether{}) + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make EtherState: %s", err) } - ipv6State, err := newIPv6State(outgoingIPv6, incomingIPv6) + ipv6State, err := n.newIPv6State(outgoingIPv6, incomingIPv6) if err != nil { t.Fatalf("can't make IPv6State: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } @@ -951,26 +948,26 @@ func (conn *IPv6Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Durat type UDPIPv4 Connection // NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults. -func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 { +func (n *DUTTestNet) NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 { t.Helper() - etherState, err := newEtherState(Ether{}, Ether{}) + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make etherState: %s", err) } - ipv4State, err := newIPv4State(IPv4{}, IPv4{}) + ipv4State, err := n.newIPv4State(IPv4{}, IPv4{}) if err != nil { t.Fatalf("can't make ipv4State: %s", err) } - udpState, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP) + udpState, err := n.newUDPState(unix.AF_INET, outgoingUDP, incomingUDP) if err != nil { t.Fatalf("can't make udpState: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } @@ -1075,26 +1072,26 @@ func (conn *UDPIPv4) Drain(t *testing.T) { type UDPIPv6 Connection // NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults. -func NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 { +func (n *DUTTestNet) NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 { t.Helper() - etherState, err := newEtherState(Ether{}, Ether{}) + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make etherState: %s", err) } - ipv6State, err := newIPv6State(IPv6{}, IPv6{}) + ipv6State, err := n.newIPv6State(IPv6{}, IPv6{}) if err != nil { t.Fatalf("can't make IPv6State: %s", err) } - udpState, err := newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP) + udpState, err := n.newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP) if err != nil { t.Fatalf("can't make udpState: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } @@ -1126,14 +1123,14 @@ func (conn *UDPIPv6) ipv6State(t *testing.T) *ipv6State { } // LocalAddr gets the local socket address of this connection. -func (conn *UDPIPv6) LocalAddr(t *testing.T) *unix.SockaddrInet6 { +func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *unix.SockaddrInet6 { t.Helper() sa := &unix.SockaddrInet6{ Port: int(*conn.udpState(t).out.SrcPort), // Local address is in perspective to the remote host, so it's scoped to the // ID of the remote interface. - ZoneId: uint32(RemoteInterfaceID), + ZoneId: zoneID, } copy(sa.Addr[:], *conn.ipv6State(t).out.SrcAddr) return sa @@ -1203,24 +1200,24 @@ func (conn *UDPIPv6) Drain(t *testing.T) { type TCPIPv6 Connection // NewTCPIPv6 creates a new TCPIPv6 connection with reasonable defaults. -func NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 { - etherState, err := newEtherState(Ether{}, Ether{}) +func (n *DUTTestNet) NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 { + etherState, err := n.newEtherState(Ether{}, Ether{}) if err != nil { t.Fatalf("can't make etherState: %s", err) } - ipv6State, err := newIPv6State(IPv6{}, IPv6{}) + ipv6State, err := n.newIPv6State(IPv6{}, IPv6{}) if err != nil { t.Fatalf("can't make ipv6State: %s", err) } - tcpState, err := newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP) + tcpState, err := n.newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP) if err != nil { t.Fatalf("can't make tcpState: %s", err) } - injector, err := NewInjector(t) + injector, err := n.NewInjector(t) if err != nil { t.Fatalf("can't make injector: %s", err) } - sniffer, err := NewSniffer(t) + sniffer, err := n.NewSniffer(t) if err != nil { t.Fatalf("can't make sniffer: %s", err) } diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 6165ab293..66a0255b8 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -17,9 +17,8 @@ package testbench import ( "context" "encoding/binary" - "flag" + "fmt" "net" - "strconv" "syscall" "testing" "time" @@ -35,18 +34,26 @@ import ( type DUT struct { conn *grpc.ClientConn posixServer POSIXClient + Net *DUTTestNet } // NewDUT creates a new connection with the DUT over gRPC. func NewDUT(t *testing.T) DUT { t.Helper() + n := GetDUTTestNet() + dut := n.ConnectToDUT(t) + t.Cleanup(func() { + dut.TearDownConnection() + dut.Net.Release() + }) + return dut +} - flag.Parse() - if err := genPseudoFlags(); err != nil { - t.Fatal("generating psuedo flags:", err) - } +// ConnectToDUT connects to DUT through gRPC. +func (n *DUTTestNet) ConnectToDUT(t *testing.T) DUT { + t.Helper() - posixServerAddress := POSIXServerIP + ":" + strconv.Itoa(POSIXServerPort) + posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.POSIXServerPort)) conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive})) if err != nil { t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err) @@ -55,11 +62,12 @@ func NewDUT(t *testing.T) DUT { return DUT{ conn: conn, posixServer: posixServer, + Net: n, } } -// TearDown closes the underlying connection. -func (dut *DUT) TearDown() { +// TearDownConnection closes the underlying connection. +func (dut *DUT) TearDownConnection() { dut.conn.Close() } @@ -132,7 +140,7 @@ func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) ( fd = dut.Socket(t, unix.AF_INET6, typ, proto) sa := unix.SockaddrInet6{} copy(sa.Addr[:], addr.To16()) - sa.ZoneId = uint32(RemoteInterfaceID) + sa.ZoneId = dut.Net.RemoteDevID dut.Bind(t, fd, &sa) } else { t.Fatalf("invalid IP address: %s", addr) @@ -154,7 +162,7 @@ func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) ( func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32, uint16) { t.Helper() - fd, remotePort := dut.CreateBoundSocket(t, typ, proto, net.ParseIP(RemoteIPv4)) + fd, remotePort := dut.CreateBoundSocket(t, typ, proto, dut.Net.RemoteIPv4) dut.Listen(t, fd, backlog) return fd, remotePort } @@ -717,9 +725,9 @@ func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Dur dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf) } -// Shutdown calls shutdown on the DUT and causes a fatal test failure if it doesn't -// succeed. If more control over the timeout or error handling is needed, use -// ShutdownWithErrno. +// Shutdown calls shutdown on the DUT and causes a fatal test failure if it +// doesn't succeed. If more control over the timeout or error handling is +// needed, use ShutdownWithErrno. func (dut *DUT) Shutdown(t *testing.T, fd, how int32) error { t.Helper() diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go index 7401a1991..19e6b8d7d 100644 --- a/test/packetimpact/testbench/layers.go +++ b/test/packetimpact/testbench/layers.go @@ -298,14 +298,12 @@ 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 { - hdrLen += l.Options.SizeWithPadding() + 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 { - // While ToBytes can be called on packets that were received as well - // as packets locally generated, it is physically impossible for a - // received packet to overflow this value so any such failure must - // be the result of a local programming error and not remotely - // triggered. A panic is therefore appropriate. - panic(fmt.Sprintf("IPv4 Options %d bytes, Max %d", len(*l.Options), header.IPv4MaximumOptionsSize)) + return nil, fmt.Errorf("IPv4 Options %d bytes, Max %d", len(*l.Options), header.IPv4MaximumOptionsSize) } } b := make([]byte, hdrLen) @@ -323,10 +321,6 @@ func (l *IPv4) ToBytes() ([]byte, error) { DstAddr: tcpip.Address(""), Options: nil, } - // Leave an empty options slice as nil. - if hdrLen > header.IPv4MinimumSize { - fields.Options = *l.Options - } if l.TOS != nil { fields.TOS = *l.TOS } @@ -373,18 +367,31 @@ func (l *IPv4) ToBytes() ([]byte, error) { if l.DstAddr != nil { fields.DstAddr = *l.DstAddr } - if l.Checksum != nil { - fields.Checksum = *l.Checksum - } + h.Encode(fields) - if l.Checksum == nil { - h.SetChecksum(^h.CalculateChecksum()) + + // 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 } @@ -498,13 +505,13 @@ func (l *IPv6) ToBytes() ([]byte, error) { } } if l.NextHeader != nil { - fields.NextHeader = *l.NextHeader + fields.TransportProtocol = tcpip.TransportProtocolNumber(*l.NextHeader) } else { nh, err := nextHeaderByLayer(l.next()) if err != nil { return nil, err } - fields.NextHeader = nh + fields.TransportProtocol = tcpip.TransportProtocolNumber(nh) } if l.HopLimit != nil { fields.HopLimit = *l.HopLimit @@ -830,7 +837,9 @@ func (l *ICMPv6) ToBytes() ([]byte, error) { if l.Code != nil { h.SetCode(*l.Code) } - copy(h.NDPPayload(), l.Payload) + if n := copy(h.MessageBody(), l.Payload); n != len(l.Payload) { + panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload))) + } if l.Checksum != nil { h.SetChecksum(*l.Checksum) } else { @@ -876,7 +885,7 @@ func parseICMPv6(b []byte) (Layer, layerParser) { Type: ICMPv6Type(h.Type()), Code: ICMPv6Code(h.Code()), Checksum: Uint16(h.Checksum()), - Payload: h.NDPPayload(), + Payload: h.MessageBody(), } return &icmpv6, nil } diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go index 193bb2dc8..1ac96626a 100644 --- a/test/packetimpact/testbench/rawsockets.go +++ b/test/packetimpact/testbench/rawsockets.go @@ -38,13 +38,27 @@ func htons(x uint16) uint16 { } // NewSniffer creates a Sniffer connected to *device. -func NewSniffer(t *testing.T) (Sniffer, error) { +func (n *DUTTestNet) NewSniffer(t *testing.T) (Sniffer, error) { t.Helper() + ifInfo, err := net.InterfaceByName(n.LocalDevName) + if err != nil { + return Sniffer{}, err + } + + var haddr [8]byte + copy(haddr[:], ifInfo.HardwareAddr) + sa := unix.SockaddrLinklayer{ + Protocol: htons(unix.ETH_P_ALL), + Ifindex: ifInfo.Index, + } snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL))) if err != nil { return Sniffer{}, err } + if err := unix.Bind(snifferFd, &sa); err != nil { + return Sniffer{}, err + } if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, 1); err != nil { t.Fatalf("can't set sockopt SO_RCVBUFFORCE to 1: %s", err) } @@ -60,7 +74,8 @@ func NewSniffer(t *testing.T) (Sniffer, error) { // packet too large for the buffer arrives, the test will get a fatal error. const maxReadSize int = 65536 -// Recv tries to read one frame until the timeout is up. +// Recv tries to read one frame until the timeout is up. If the timeout given +// is 0, then no read attempt will be made. func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte { t.Helper() @@ -73,9 +88,13 @@ func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte { whole, frac := math.Modf(timeout.Seconds()) tv := unix.Timeval{ Sec: int64(whole), - Usec: int64(frac * float64(time.Microsecond/time.Second)), + Usec: int64(frac * float64(time.Second/time.Microsecond)), + } + // The following should never happen, but having this guard here is better + // than blocking indefinitely in the future. + if tv.Sec == 0 && tv.Usec == 0 { + t.Fatal("setting SO_RCVTIMEO to 0 means blocking indefinitely") } - if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil { t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err) } @@ -136,10 +155,10 @@ type Injector struct { } // NewInjector creates a new injector on *device. -func NewInjector(t *testing.T) (Injector, error) { +func (n *DUTTestNet) NewInjector(t *testing.T) (Injector, error) { t.Helper() - ifInfo, err := net.InterfaceByName(LocalDevice) + ifInfo, err := net.InterfaceByName(n.LocalDevName) if err != nil { return Injector{}, err } @@ -147,7 +166,7 @@ func NewInjector(t *testing.T) (Injector, error) { var haddr [8]byte copy(haddr[:], ifInfo.HardwareAddr) sa := unix.SockaddrLinklayer{ - Protocol: unix.ETH_P_IP, + Protocol: htons(unix.ETH_P_IP), Ifindex: ifInfo.Index, Halen: uint8(len(ifInfo.HardwareAddr)), Addr: haddr, diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go index c1db95d8c..891897d55 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -17,108 +17,105 @@ package testbench import ( + "encoding/json" "flag" "fmt" "math/rand" "net" - "os/exec" "testing" "time" - - "gvisor.dev/gvisor/test/packetimpact/netdevs" ) var ( // Native indicates that the test is being run natively. Native = false - // LocalDevice is the device that testbench uses to inject traffic. - LocalDevice = "" - // RemoteDevice is the device name on the DUT, individual tests can - // use the name to construct tests. - RemoteDevice = "" + // RPCKeepalive is the gRPC keepalive. + RPCKeepalive = 10 * time.Second + // RPCTimeout is the gRPC timeout. + RPCTimeout = 100 * time.Millisecond + + // dutTestNetsJSON is the json string that describes all the test networks to + // duts available to use. + dutTestNetsJSON string + // dutTestNets is the pool among which the testbench can choose a DUT to work + // with. + dutTestNets chan *DUTTestNet +) +// DUTTestNet describes the test network setup on dut and how the testbench +// should connect with an existing DUT. +type DUTTestNet struct { + // LocalMAC is the local MAC address on the test network. + LocalMAC net.HardwareAddr + // RemoteMAC is the DUT's MAC address on the test network. + RemoteMAC net.HardwareAddr // LocalIPv4 is the local IPv4 address on the test network. - LocalIPv4 = "" + LocalIPv4 net.IP // RemoteIPv4 is the DUT's IPv4 address on the test network. - RemoteIPv4 = "" + RemoteIPv4 net.IP // IPv4PrefixLength is the network prefix length of the IPv4 test network. - IPv4PrefixLength = 0 - + IPv4PrefixLength int // LocalIPv6 is the local IPv6 address on the test network. - LocalIPv6 = "" + LocalIPv6 net.IP // RemoteIPv6 is the DUT's IPv6 address on the test network. - RemoteIPv6 = "" + RemoteIPv6 net.IP + // LocalDevID is the ID of the local interface on the test network. + LocalDevID uint32 + // RemoteDevID is the ID of the remote interface on the test network. + RemoteDevID uint32 + // LocalDevName is the device that testbench uses to inject traffic. + LocalDevName string + // RemoteDevName is the device name on the DUT, individual tests can + // use the name to construct tests. + RemoteDevName string - // LocalInterfaceID is the ID of the local interface on the test network. - LocalInterfaceID uint32 - // RemoteInterfaceID is the ID of the remote interface on the test network. - // - // Not using uint32 because package flag does not support uint32. - RemoteInterfaceID uint64 - - // LocalMAC is the local MAC address on the test network. - LocalMAC = "" - // RemoteMAC is the DUT's MAC address on the test network. - RemoteMAC = "" + // The following two fields on actually on the control network instead + // of the test network, including them for convenience. // POSIXServerIP is the POSIX server's IP address on the control network. - POSIXServerIP = "" + POSIXServerIP net.IP // POSIXServerPort is the UDP port the POSIX server is bound to on the // control network. - POSIXServerPort = 40000 - - // RPCKeepalive is the gRPC keepalive. - RPCKeepalive = 10 * time.Second - // RPCTimeout is the gRPC timeout. - RPCTimeout = 100 * time.Millisecond -) + POSIXServerPort uint16 +} -// RegisterFlags defines flags and associates them with the package-level +// registerFlags defines flags and associates them with the package-level // exported variables above. It should be called by tests in their init // functions. -func RegisterFlags(fs *flag.FlagSet) { - fs.StringVar(&POSIXServerIP, "posix_server_ip", POSIXServerIP, "ip address to listen to for UDP commands") - fs.IntVar(&POSIXServerPort, "posix_server_port", POSIXServerPort, "port to listen to for UDP commands") +func registerFlags(fs *flag.FlagSet) { + fs.BoolVar(&Native, "native", Native, "whether the test is running natively") fs.DurationVar(&RPCTimeout, "rpc_timeout", RPCTimeout, "gRPC timeout") fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive") - fs.StringVar(&LocalIPv4, "local_ipv4", LocalIPv4, "local IPv4 address for test packets") - fs.StringVar(&RemoteIPv4, "remote_ipv4", RemoteIPv4, "remote IPv4 address for test packets") - fs.StringVar(&RemoteIPv6, "remote_ipv6", RemoteIPv6, "remote IPv6 address for test packets") - fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets") - fs.StringVar(&LocalDevice, "local_device", LocalDevice, "local device to inject traffic") - fs.StringVar(&RemoteDevice, "remote_device", RemoteDevice, "remote device on the DUT") - fs.BoolVar(&Native, "native", Native, "whether the test is running natively") - fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets") + fs.StringVar(&dutTestNetsJSON, "dut_test_nets_json", dutTestNetsJSON, "path to the dut test nets json file") } -// genPseudoFlags populates flag-like global config based on real flags. -// -// genPseudoFlags must only be called after flag.Parse. -func genPseudoFlags() error { - out, err := exec.Command("ip", "addr", "show").CombinedOutput() - if err != nil { - return fmt.Errorf("listing devices: %q: %w", string(out), err) - } - devs, err := netdevs.ParseDevices(string(out)) - if err != nil { - return fmt.Errorf("parsing devices: %w", err) +// Initialize initializes the testbench, it parse the flags and sets up the +// pool of test networks for testbench's later use. +func Initialize(fs *flag.FlagSet) { + registerFlags(fs) + flag.Parse() + if err := loadDUTTestNets(); err != nil { + panic(err) } +} - _, deviceInfo, err := netdevs.FindDeviceByIP(net.ParseIP(LocalIPv4), devs) - if err != nil { - return fmt.Errorf("can't find deviceInfo: %w", err) +// loadDUTTestNets loads available DUT test networks from the json file, it +// must be called after flag.Parse(). +func loadDUTTestNets() error { + var parsedTestNets []DUTTestNet + if err := json.Unmarshal([]byte(dutTestNetsJSON), &parsedTestNets); err != nil { + return fmt.Errorf("failed to unmarshal JSON: %w", err) } - - LocalMAC = deviceInfo.MAC.String() - LocalIPv6 = deviceInfo.IPv6Addr.String() - LocalInterfaceID = deviceInfo.ID - - if deviceInfo.IPv4Net != nil { - IPv4PrefixLength, _ = deviceInfo.IPv4Net.Mask.Size() - } else { - IPv4PrefixLength, _ = net.ParseIP(LocalIPv4).DefaultMask().Size() + if got, want := len(parsedTestNets), 1; got < want { + return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want) + } + // Using a buffered channel as semaphore + dutTestNets = make(chan *DUTTestNet, len(parsedTestNets)) + for i := range parsedTestNets { + parsedTestNets[i].LocalIPv4 = parsedTestNets[i].LocalIPv4.To4() + parsedTestNets[i].RemoteIPv4 = parsedTestNets[i].RemoteIPv4.To4() + dutTestNets <- &parsedTestNets[i] } - return nil } @@ -132,3 +129,15 @@ func GenerateRandomPayload(t *testing.T, n int) []byte { } return buf } + +// GetDUTTestNet gets a usable DUTTestNet, the function will block until any +// becomes available. +func GetDUTTestNet() *DUTTestNet { + return <-dutTestNets +} + +// Release releases the DUTTestNet back to the pool so that some other test +// can use. +func (n *DUTTestNet) Release() { + dutTestNets <- n +} diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 33bd070c1..b1b3c578b 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -366,9 +366,29 @@ packetimpact_testbench( ], ) +packetimpact_testbench( + name = "tcp_zero_receive_window", + srcs = ["tcp_zero_receive_window_test.go"], + deps = [ + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + validate_all_tests() [packetimpact_go_test( name = t.name, expect_netstack_failure = hasattr(t, "expect_netstack_failure"), + num_duts = t.num_duts if hasattr(t, "num_duts") else 1, ) for t in ALL_TESTS] + +test_suite( + name = "all_tests", + tags = [ + "manual", + "packetimpact", + ], + tests = existing_rules(), +) diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go index a61054c2c..11f0fcd1e 100644 --- a/test/packetimpact/tests/fin_wait2_timeout_test.go +++ b/test/packetimpact/tests/fin_wait2_timeout_test.go @@ -25,7 +25,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestFinWait2Timeout(t *testing.T) { @@ -38,10 +38,9 @@ func TestFinWait2Timeout(t *testing.T) { } { t.Run(tt.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go index 2d59d552d..40d7a491d 100644 --- a/test/packetimpact/tests/icmpv6_param_problem_test.go +++ b/test/packetimpact/tests/icmpv6_param_problem_test.go @@ -25,15 +25,14 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestICMPv6ParamProblemTest sends a packet with a bad next header. The DUT // should respond with an ICMPv6 Parameter Problem message. func TestICMPv6ParamProblemTest(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) defer conn.Close(t) ipv6 := testbench.IPv6{ // 254 is reserved and used for experimentation and testing. This should diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go index 40f899065..d2203082d 100644 --- a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go +++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go @@ -27,17 +27,17 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } type fragmentInfo struct { offset uint16 size uint16 more uint8 + id uint16 } func TestIPv4FragmentReassembly(t *testing.T) { - const fragmentID = 42 icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber) tests := []struct { @@ -45,32 +45,78 @@ func TestIPv4FragmentReassembly(t *testing.T) { ipPayloadLen int fragments []fragmentInfo expectReply bool + skip bool + skipReason string }{ { description: "basic reassembly", - ipPayloadLen: 2000, + ipPayloadLen: 3000, fragments: []fragmentInfo{ - {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, - {offset: 1000, size: 1000, more: 0}, + {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: 2000, + ipPayloadLen: 3000, fragments: []fragmentInfo{ - {offset: 1000, size: 1000, more: 0}, - {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, + {offset: 2000, size: 1000, id: 6, more: 0}, + {offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments}, }, expectReply: true, }, + { + description: "duplicated fragments", + ipPayloadLen: 3000, + fragments: []fragmentInfo{ + {offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments}, + {offset: 2000, size: 1000, id: 7, more: 0}, + }, + expectReply: true, + skip: true, + skipReason: "gvisor.dev/issues/4971", + }, + { + description: "fragment subset", + ipPayloadLen: 3000, + fragments: []fragmentInfo{ + {offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments}, + {offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments}, + {offset: 2000, size: 1000, id: 8, more: 0}, + }, + expectReply: true, + skip: true, + skipReason: "gvisor.dev/issues/4971", + }, + { + description: "fragment overlap", + ipPayloadLen: 3000, + fragments: []fragmentInfo{ + {offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, + {offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments}, + {offset: 2000, size: 1000, id: 9, more: 0}, + }, + expectReply: false, + skip: true, + skipReason: "gvisor.dev/issues/4971", + }, } for _, test := range tests { + if test.skip { + t.Skip("%s test skipped: %s", test.description, test.skipReason) + continue + } t.Run(test.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - conn := testbench.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) + conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) defer conn.Close(t) data := make([]byte, test.ipPayloadLen) @@ -96,7 +142,7 @@ func TestIPv4FragmentReassembly(t *testing.T) { Protocol: &icmpv4ProtoNum, FragmentOffset: testbench.Uint16(fragment.offset), Flags: testbench.Uint8(fragment.more), - ID: testbench.Uint16(fragmentID), + ID: testbench.Uint16(fragment.id), }, &testbench.Payload{ Bytes: data[fragment.offset:][:fragment.size], @@ -115,7 +161,7 @@ func TestIPv4FragmentReassembly(t *testing.T) { }, time.Second) if err != nil { // Either an unexpected frame was received, or none at all. - if bytesReceived < test.ipPayloadLen { + 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 diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go index 7f7a768d3..a63b41366 100644 --- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go +++ b/test/packetimpact/tests/ipv4_id_uniqueness_test.go @@ -28,7 +28,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) { @@ -67,12 +67,10 @@ func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go index e058fb0d8..a37867e85 100644 --- a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go +++ b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go @@ -16,7 +16,6 @@ package ipv6_fragment_icmp_error_test import ( "flag" - "net" "testing" "time" @@ -35,10 +34,10 @@ const ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } -func fragmentedICMPEchoRequest(t *testing.T, conn *testbench.Connection, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) { +func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.Connection, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) { t.Helper() icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize)) @@ -48,8 +47,8 @@ func fragmentedICMPEchoRequest(t *testing.T, conn *testbench.Connection, firstPa icmpv6Header.SetSequence(0) cksum := header.ICMPv6Checksum( icmpv6Header, - tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()), - tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()), + tcpip.Address(n.LocalIPv6), + tcpip.Address(n.RemoteIPv6), buffer.NewVectorisedView(len(payload), []buffer.View{payload}), ) icmpv6Header.SetChecksum(cksum) @@ -120,13 +119,13 @@ func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() dut := testbench.NewDUT(t) - defer dut.TearDown() - ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) conn := (*testbench.Connection)(&ipv6Conn) defer ipv6Conn.Close(t) - fragments, _ := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) for _, i := range test.sendFrameOrder { conn.SendFrame(t, fragments[i-1]) @@ -222,13 +221,13 @@ func TestIPv6FragmentReassemblyTimeout(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() dut := testbench.NewDUT(t) - defer dut.TearDown() - ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) conn := (*testbench.Connection)(&ipv6Conn) defer ipv6Conn.Close(t) - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) for _, i := range test.sendFrameOrder { conn.SendFrame(t, fragments[i-1]) @@ -318,13 +317,13 @@ func TestIPv6FragmentParamProblem(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() dut := testbench.NewDUT(t) - defer dut.TearDown() - ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) conn := (*testbench.Connection)(&ipv6Conn) defer ipv6Conn.Close(t) - fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) for _, i := range test.sendFrameOrder { conn.SendFrame(t, fragments[i-1]) diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go index eb56a53f7..dd98ee7a1 100644 --- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go +++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go @@ -17,7 +17,6 @@ package ipv6_fragment_reassembly_test import ( "flag" "math/rand" - "net" "testing" "time" @@ -29,17 +28,17 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } type fragmentInfo struct { offset uint16 size uint16 more bool + id uint32 } func TestIPv6FragmentReassembly(t *testing.T) { - const fragmentID = 42 icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) tests := []struct { @@ -50,10 +49,11 @@ func TestIPv6FragmentReassembly(t *testing.T) { }{ { description: "basic reassembly", - ipPayloadLen: 1500, + ipPayloadLen: 3000, fragments: []fragmentInfo{ - {offset: 0, size: 760, more: true}, - {offset: 760, size: 740, more: false}, + {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, }, @@ -61,23 +61,55 @@ func TestIPv6FragmentReassembly(t *testing.T) { description: "out of order fragments", ipPayloadLen: 3000, fragments: []fragmentInfo{ - {offset: 0, size: 1024, more: true}, - {offset: 2048, size: 952, more: false}, - {offset: 1024, size: 1024, more: true}, + {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) - defer dut.TearDown() - conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) defer conn.Close(t) - lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()) - rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()) + lIP := tcpip.Address(dut.Net.LocalIPv6) + rIP := tcpip.Address(dut.Net.RemoteIPv6) data := make([]byte, test.ipPayloadLen) icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize]) @@ -103,7 +135,7 @@ func TestIPv6FragmentReassembly(t *testing.T) { NextHeader: &icmpv6ProtoNum, FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit), MoreFragments: testbench.Bool(fragment.more), - Identification: testbench.Uint32(fragmentID), + Identification: testbench.Uint32(fragment.id), }, &testbench.Payload{ Bytes: data[fragment.offset:][:fragment.size], @@ -120,7 +152,7 @@ func TestIPv6FragmentReassembly(t *testing.T) { }, time.Second) if err != nil { // Either an unexpected frame was received, or none at all. - if bytesReceived < test.ipPayloadLen { + 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 diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go index e79d74476..cb5396417 100644 --- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go +++ b/test/packetimpact/tests/ipv6_unknown_options_action_test.go @@ -27,7 +27,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer { @@ -141,8 +141,7 @@ func TestIPv6UnknownOptionAction(t *testing.T) { } { t.Run(tt.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) conn := (*testbench.Connection)(&ipv6Conn) defer ipv6Conn.Close(t) diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go index 8feea4a82..a7ba5035e 100644 --- a/test/packetimpact/tests/tcp_cork_mss_test.go +++ b/test/packetimpact/tests/tcp_cork_mss_test.go @@ -25,16 +25,15 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTCPCorkMSS tests for segment coalesce and split as per MSS. func TestTCPCorkMSS(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) const mss = uint32(header.TCPDefaultMSS) diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go index 22937d92f..5d1266f3c 100644 --- a/test/packetimpact/tests/tcp_handshake_window_size_test.go +++ b/test/packetimpact/tests/tcp_handshake_window_size_test.go @@ -25,17 +25,16 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTCPHandshakeWindowSize tests if the stack is honoring the window size // communicated during handshake. func TestTCPHandshakeWindowSize(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) // Start handshake with zero window size. diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go index b9a0409aa..bc4b64388 100644 --- a/test/packetimpact/tests/tcp_linger_test.go +++ b/test/packetimpact/tests/tcp_linger_test.go @@ -27,12 +27,12 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) { listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) conn.Connect(t) acceptFD, _ := dut.Accept(t, listenFD) return acceptFD, listenFD, conn @@ -41,7 +41,6 @@ func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPI func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) { conn.Close(t) dut.Close(t, listenFD) - dut.TearDown() } // lingerDuration is the timeout value used with SO_LINGER socket option. @@ -266,5 +265,4 @@ func TestTCPLingerNonEstablished(t *testing.T) { if diff > lingerDuration { t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff) } - dut.TearDown() } diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go index 8a1fe1279..6cd6d2edf 100644 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ b/test/packetimpact/tests/tcp_network_unreachable_test.go @@ -17,7 +17,6 @@ package tcp_synsent_reset_test import ( "context" "flag" - "net" "syscall" "testing" "time" @@ -28,7 +27,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTCPSynSentUnreachable verifies that TCP connections fail immediately when @@ -37,17 +36,16 @@ func init() { func TestTCPSynSentUnreachable(t *testing.T) { // Create the DUT and connection. dut := testbench.NewDUT(t) - defer dut.TearDown() - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4)) + clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) port := uint16(9001) - conn := testbench.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) defer conn.Close(t) // Bring the DUT to SYN-SENT state with a non-blocking connect. ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout) defer cancel() sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv4)).To4()) + copy(sa.Addr[:], dut.Net.LocalIPv4) if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) { t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err) } @@ -91,9 +89,8 @@ func TestTCPSynSentUnreachable(t *testing.T) { func TestTCPSynSentUnreachable6(t *testing.T) { // Create the DUT and connection. dut := testbench.NewDUT(t) - defer dut.TearDown() - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv6)) - conn := testbench.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) + clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6) + conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) defer conn.Close(t) // Bring the DUT to SYN-SENT state with a non-blocking connect. @@ -101,9 +98,9 @@ func TestTCPSynSentUnreachable6(t *testing.T) { defer cancel() sa := unix.SockaddrInet6{ Port: int(conn.SrcPort()), - ZoneId: uint32(testbench.RemoteInterfaceID), + ZoneId: dut.Net.RemoteDevID, } - copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv6)).To16()) + copy(sa.Addr[:], dut.Net.LocalIPv6) if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) { t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err) } diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go index 82b7a85ff..f0af5352d 100644 --- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go +++ b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go @@ -25,14 +25,13 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestTcpNoAcceptCloseReset(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) conn.Connect(t) defer conn.Close(t) dut.Close(t, listenFd) diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go index 08f759f7c..1b041932a 100644 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ b/test/packetimpact/tests/tcp_outside_the_window_test.go @@ -27,7 +27,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive @@ -62,10 +62,9 @@ func TestTCPOutsideTheWindow(t *testing.T) { } { t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) acceptFD, _ := dut.Accept(t, listenFD) diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go index 37f3b56dd..24d9ef4ec 100644 --- a/test/packetimpact/tests/tcp_paws_mechanism_test.go +++ b/test/packetimpact/tests/tcp_paws_mechanism_test.go @@ -26,15 +26,14 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestPAWSMechanism(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) options := make([]byte, header.TCPOptionTSLength) diff --git a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go index d9f3ea0f2..646c93216 100644 --- a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go +++ b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "errors" "flag" - "net" "sync" "syscall" "testing" @@ -32,7 +31,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestQueueReceiveInSynSent tests receive behavior when the TCP state @@ -50,10 +49,9 @@ func TestQueueReceiveInSynSent(t *testing.T) { } { t.Run(tt.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4)) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) sampleData := []byte("Sample Data") diff --git a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go index 0ec8fd748..29e51cae3 100644 --- a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go +++ b/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go @@ -18,7 +18,6 @@ import ( "context" "errors" "flag" - "net" "sync" "syscall" "testing" @@ -30,7 +29,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestQueueSendInSynSent tests send behavior when the TCP state @@ -48,10 +47,9 @@ func TestQueueSendInSynSent(t *testing.T) { } { t.Run(tt.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4)) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) sampleData := []byte("Sample Data") diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go index cfbba1e8e..d6ad5cda6 100644 --- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go +++ b/test/packetimpact/tests/tcp_rcv_buf_space_test.go @@ -26,7 +26,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestReduceRecvBuf tests that a packet within window is still dropped @@ -34,10 +34,9 @@ func init() { // segment. func TestReduceRecvBuf(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_reordering_test.go b/test/packetimpact/tests/tcp_reordering_test.go index b4aeaab57..ca352dbc7 100644 --- a/test/packetimpact/tests/tcp_reordering_test.go +++ b/test/packetimpact/tests/tcp_reordering_test.go @@ -22,19 +22,18 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/seqnum" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" + "gvisor.dev/gvisor/test/packetimpact/testbench" ) func init() { - tb.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestReorderingWindow(t *testing.T) { - dut := tb.NewDUT(t) - defer dut.TearDown() + dut := testbench.NewDUT(t) listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) // Enable SACK. @@ -54,13 +53,13 @@ func TestReorderingWindow(t *testing.T) { acceptFd, _ := dut.Accept(t, listenFd) defer dut.Close(t, acceptFd) - if tb.Native { + if testbench.Native { // Linux has changed its handling of reordering, force the old behavior. dut.SetSockOpt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_CONGESTION, []byte("reno")) } pls := dut.GetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_MAXSEG) - if !tb.Native { + if !testbench.Native { // netstack does not impliment TCP_MAXSEG correctly. Fake it // here. Netstack uses the max SACK size which is 32. The MSS // option is 8 bytes, making the total 36 bytes. @@ -75,14 +74,14 @@ func TestReorderingWindow(t *testing.T) { for i, sn := 0, seqNum1; i < numPkts; i++ { dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second) + gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second) sn.UpdateForward(seqnum.Size(len(payload))) if err != nil { - t.Errorf("Expect #%d: %s", i+1, err) + t.Fatalf("Expect #%d: %s", i+1, err) continue } if gotOne == nil { - t.Errorf("#%d: expected a packet within a second but got none", i+1) + t.Fatalf("#%d: expected a packet within a second but got none", i+1) } } @@ -97,13 +96,13 @@ func TestReorderingWindow(t *testing.T) { seqNum1.Add(seqnum.Size(len(payload))), seqNum1.Add(seqnum.Size(4 * len(payload))), }}, sackBlock[sbOff:]) - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) // ACK first packet. - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum1) + uint32(len(payload)))}) + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1) + uint32(len(payload)))}) // Check for retransmit. - gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(seqNum1))}, time.Second) + gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second) if err != nil { t.Error("Expect for retransmit:", err) } @@ -123,29 +122,29 @@ func TestReorderingWindow(t *testing.T) { seqNum1.Add(seqnum.Size(4 * len(payload))), }}, dsackBlock[dsbOff:]) - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum2)), Options: dsackBlock[:dsbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum2)), Options: dsackBlock[:dsbOff]}) // Send half of the original window of packets, checking that we // received each. for i, sn := 0, seqNum2; i < numPkts/2; i++ { dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second) + gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second) sn.UpdateForward(seqnum.Size(len(payload))) if err != nil { - t.Errorf("Expect #%d: %s", i+1, err) + t.Fatalf("Expect #%d: %s", i+1, err) continue } if gotOne == nil { - t.Errorf("#%d: expected a packet within a second but got none", i+1) + t.Fatalf("#%d: expected a packet within a second but got none", i+1) } } - if !tb.Native { + if !testbench.Native { // The window should now be halved, so we should receive any // more, even if we send them. dut.Send(t, acceptFd, payload, 0) - if got, err := conn.Expect(t, tb.TCP{}, 100*time.Millisecond); got != nil || err == nil { + if got, err := conn.Expect(t, testbench.TCP{}, 100*time.Millisecond); got != nil || err == nil { t.Fatalf("expected no packets within 100 millisecond, but got one: %s", got) } return @@ -155,20 +154,20 @@ func TestReorderingWindow(t *testing.T) { for i, sn := 0, seqNum2.Add(seqnum.Size(numPkts/2*len(payload))); i < 2; i++ { dut.Send(t, acceptFd, payload, 0) - gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second) + gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second) sn.UpdateForward(seqnum.Size(len(payload))) if err != nil { - t.Errorf("Expect #%d: %s", i+1, err) + t.Fatalf("Expect #%d: %s", i+1, err) continue } if gotOne == nil { - t.Errorf("#%d: expected a packet within a second but got none", i+1) + t.Fatalf("#%d: expected a packet within a second but got none", i+1) } } // The window should now be full. dut.Send(t, acceptFd, payload, 0) - if got, err := conn.Expect(t, tb.TCP{}, 100*time.Millisecond); got != nil || err == nil { + if got, err := conn.Expect(t, testbench.TCP{}, 100*time.Millisecond); got != nil || err == nil { t.Fatalf("expected no packets within 100 millisecond, but got one: %s", got) } } diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go index 072014ff8..27e9641b1 100644 --- a/test/packetimpact/tests/tcp_retransmits_test.go +++ b/test/packetimpact/tests/tcp_retransmits_test.go @@ -25,17 +25,16 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestRetransmits tests retransmits occur at exponentially increasing // time intervals. func TestRetransmits(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go index f91b06ba1..418393796 100644 --- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go +++ b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go @@ -16,7 +16,6 @@ package tcp_send_window_sizes_piggyback_test import ( "flag" - "fmt" "testing" "time" @@ -26,7 +25,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestSendWindowSizesPiggyback tests cases where segment sizes are close to @@ -58,13 +57,12 @@ func TestSendWindowSizesPiggyback(t *testing.T) { // greater than available sender window. {"WindowGreaterThanSegment", segmentSize + 1, sampleData, sampleData, true /* enqueue */}, } { - t.Run(fmt.Sprintf("%s%d", tt.description, tt.windowSize), func(t *testing.T) { + t.Run(tt.description, func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go index 57d034dd1..c5bbd29ee 100644 --- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go +++ b/test/packetimpact/tests/tcp_synrcvd_reset_test.go @@ -25,16 +25,15 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTCPSynRcvdReset tests transition from SYN-RCVD to CLOSED. func TestTCPSynRcvdReset(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) // Expect dut connection to have transitioned to SYN-RCVD state. diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go index eac8eb19d..2c8bb101b 100644 --- a/test/packetimpact/tests/tcp_synsent_reset_test.go +++ b/test/packetimpact/tests/tcp_synsent_reset_test.go @@ -16,34 +16,33 @@ package tcp_synsent_reset_test import ( "flag" - "net" "testing" "time" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/tcpip/header" - tb "gvisor.dev/gvisor/test/packetimpact/testbench" + "gvisor.dev/gvisor/test/packetimpact/testbench" ) func init() { - tb.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // dutSynSentState sets up the dut connection in SYN-SENT state. -func dutSynSentState(t *testing.T) (*tb.DUT, *tb.TCPIPv4, uint16, uint16) { +func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, uint16) { t.Helper() - dut := tb.NewDUT(t) + dut := testbench.NewDUT(t) - clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(tb.RemoteIPv4)) + clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) port := uint16(9001) - conn := tb.NewTCPIPv4(t, tb.TCP{SrcPort: &port, DstPort: &clientPort}, tb.TCP{SrcPort: &clientPort, DstPort: &port}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) sa := unix.SockaddrInet4{Port: int(port)} - copy(sa.Addr[:], net.IP(net.ParseIP(tb.LocalIPv4)).To4()) + copy(sa.Addr[:], dut.Net.LocalIPv4) // Bring the dut to SYN-SENT state with a non-blocking connect. dut.Connect(t, clientFD, &sa) - if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn)}, nil, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second); err != nil { t.Fatalf("expected SYN\n") } @@ -52,14 +51,13 @@ func dutSynSentState(t *testing.T) (*tb.DUT, *tb.TCPIPv4, uint16, uint16) { // TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition. func TestTCPSynSentReset(t *testing.T) { - dut, conn, _, _ := dutSynSentState(t) + _, conn, _, _ := dutSynSentState(t) defer conn.Close(t) - defer dut.TearDown() - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) // Expect the connection to have closed. // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Fatalf("expected a TCP RST") } } @@ -68,23 +66,22 @@ func TestTCPSynSentReset(t *testing.T) { // transitions. func TestTCPSynSentRcvdReset(t *testing.T) { dut, c, remotePort, clientPort := dutSynSentState(t) - defer dut.TearDown() defer c.Close(t) - conn := tb.NewTCPIPv4(t, tb.TCP{SrcPort: &remotePort, DstPort: &clientPort}, tb.TCP{SrcPort: &clientPort, DstPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort}) defer conn.Close(t) // Initiate new SYN connection with the same port pair // (simultaneous open case), expect the dut connection to move to // SYN-RCVD state - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected SYN-ACK %s\n", err) } - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}) + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}) // Expect the connection to have transitioned SYN-RCVD to CLOSED. // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Fatalf("expected a TCP RST") } } diff --git a/test/packetimpact/tests/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go index 2f76a6531..d1d2fb83d 100644 --- a/test/packetimpact/tests/tcp_timewait_reset_test.go +++ b/test/packetimpact/tests/tcp_timewait_reset_test.go @@ -25,16 +25,15 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestTimeWaitReset tests handling of RST when in TIME_WAIT state. func TestTimeWaitReset(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go index d078bbf15..ea962c818 100644 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go +++ b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go @@ -28,7 +28,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestEstablishedUnaccSeqAck(t *testing.T) { @@ -48,10 +48,9 @@ func TestEstablishedUnaccSeqAck(t *testing.T) { } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) @@ -102,10 +101,9 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) { } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) @@ -164,10 +162,9 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go index 551dc78e7..b16e65366 100644 --- a/test/packetimpact/tests/tcp_user_timeout_test.go +++ b/test/packetimpact/tests/tcp_user_timeout_test.go @@ -25,7 +25,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) { @@ -64,10 +64,9 @@ func TestTCPUserTimeout(t *testing.T) { t.Run(tt.description+ttf.description, func(t *testing.T) { // Create a socket, listen, TCP handshake, and accept. dut := testbench.NewDUT(t) - defer dut.TearDown() listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFD) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) acceptFD, _ := dut.Accept(t, listenFD) diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go index 5b001fbec..093484721 100644 --- a/test/packetimpact/tests/tcp_window_shrink_test.go +++ b/test/packetimpact/tests/tcp_window_shrink_test.go @@ -25,15 +25,14 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestWindowShrink(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go new file mode 100644 index 000000000..d06690705 --- /dev/null +++ b/test/packetimpact/tests/tcp_zero_receive_window_test.go @@ -0,0 +1,125 @@ +// 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.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + if err != nil { + t.Fatalf("expected packet was not received: %s", err) + } + // 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.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + if err != nil { + t.Fatalf("expected packet was not received: %s", err) + } + if 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 index da93267d6..1ab9ee1b2 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go @@ -25,17 +25,16 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestZeroWindowProbeRetransmit tests retransmits of zero window probes // to be sent at exponentially inreasing time intervals. func TestZeroWindowProbeRetransmit(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go index 44cac42f8..650a569cc 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go @@ -25,17 +25,16 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestZeroWindowProbe tests few cases of zero window probing over the // same connection. func TestZeroWindowProbe(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go index 09a1c653f..079fea68c 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go @@ -25,17 +25,16 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } // TestZeroWindowProbeUserTimeout sanity tests user timeout when we are // retransmitting zero window probes. func TestZeroWindowProbeUserTimeout(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) defer dut.Close(t, listenFd) - conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) defer conn.Close(t) conn.Connect(t) diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go index 17f32ef65..f4ae00a81 100644 --- a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go +++ b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go @@ -26,21 +26,20 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestAnyRecvUnicastUDP(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) defer dut.Close(t, boundFD) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) conn.SendIP( t, - testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(net.ParseIP(testbench.RemoteIPv4).To4()))}, + testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(dut.Net.RemoteIPv4))}, testbench.UDP{}, &testbench.Payload{Bytes: payload}, ) diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go index 3d2791a6e..52c6f9d91 100644 --- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go +++ b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go @@ -30,16 +30,15 @@ import ( var oneSecond = unix.Timeval{Sec: 1, Usec: 0} func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv4)) + remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4) defer dut.Close(t, remoteFD) dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) for _, mcastAddr := range []net.IP{ @@ -66,11 +65,10 @@ func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) { func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv6)) + remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv6) defer dut.Close(t, remoteFD) dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond) - conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) for _, mcastAddr := range []net.IP{ diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go index df35d16c8..cd4523e88 100644 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go @@ -30,7 +30,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } type connectionMode bool @@ -229,7 +229,6 @@ func TestUDPICMPErrorPropagation(t *testing.T) { } { t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) defer dut.Close(t, remoteFD) @@ -239,7 +238,7 @@ func TestUDPICMPErrorPropagation(t *testing.T) { cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) defer dut.Close(t, cleanFD) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) if connect { @@ -261,7 +260,7 @@ func TestUDPICMPErrorPropagation(t *testing.T) { // involved in the generation of the ICMP error. As such, // interactions between it and the the DUT should be independent of // the ICMP error at least at the port level. - connClean := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + connClean := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer connClean.Close(t) errDetectConn = &connClean @@ -283,7 +282,6 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) defer dut.Close(t, remoteFD) @@ -293,7 +291,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero) defer dut.Close(t, cleanFD) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) if connect { diff --git a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go index 526173969..b29c07825 100644 --- a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go +++ b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go @@ -29,12 +29,12 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } func TestUDPRecvMcastBcast(t *testing.T) { - subnetBcastAddr := broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32)) - + dut := testbench.NewDUT(t) + subnetBcastAddr := broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)) for _, v := range []struct { bound, to net.IP }{ @@ -43,17 +43,22 @@ func TestUDPRecvMcastBcast(t *testing.T) { {bound: net.IPv4zero, to: net.IPv4allsys}, {bound: subnetBcastAddr, to: subnetBcastAddr}, - {bound: subnetBcastAddr, to: net.IPv4bcast}, + + // FIXME(gvisor.dev/issue/4896): Previously by the time subnetBcastAddr is + // created, IPv4PrefixLength is still 0 because genPseudoFlags is not called + // yet, it was only called in NewDUT, so the test didn't do what the author + // original intended to and becomes failing because we process all flags at + // the very beginning. + // + // {bound: subnetBcastAddr, to: net.IPv4bcast}, {bound: net.IPv4bcast, to: net.IPv4bcast}, {bound: net.IPv4allsys, to: net.IPv4allsys}, } { t.Run(fmt.Sprintf("bound=%s,to=%s", v.bound, v.to), func(t *testing.T) { - dut := testbench.NewDUT(t) - defer dut.TearDown() boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, v.bound) defer dut.Close(t, boundFD) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) @@ -73,15 +78,14 @@ func TestUDPRecvMcastBcast(t *testing.T) { func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv4)) + boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4) dut.SetSockOptTimeval(t, boundFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 1, Usec: 0}) defer dut.Close(t, boundFD) - conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) defer conn.Close(t) for _, to := range []net.IP{ - broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32)), + broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)), net.IPv4(255, 255, 255, 255), net.IPv4(224, 0, 0, 1), } { @@ -102,9 +106,10 @@ func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) { } func broadcastAddr(ip net.IP, mask net.IPMask) net.IP { + result := make(net.IP, net.IPv4len) ip4 := ip.To4() for i := range ip4 { - ip4[i] |= ^mask[i] + result[i] = ip4[i] | ^mask[i] } - return ip4 + return result } diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go index 91b967400..7ee2c8014 100644 --- a/test/packetimpact/tests/udp_send_recv_dgram_test.go +++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go @@ -26,7 +26,7 @@ import ( ) func init() { - testbench.RegisterFlags(flag.CommandLine) + testbench.Initialize(flag.CommandLine) } type udpConn interface { @@ -38,7 +38,6 @@ type udpConn interface { func TestUDP(t *testing.T) { dut := testbench.NewDUT(t) - defer dut.TearDown() for _, isIPv4 := range []bool{true, false} { ipVersionName := "IPv6" @@ -46,24 +45,24 @@ func TestUDP(t *testing.T) { ipVersionName = "IPv4" } t.Run(ipVersionName, func(t *testing.T) { - var addr string + var addr net.IP if isIPv4 { - addr = testbench.RemoteIPv4 + addr = dut.Net.RemoteIPv4 } else { - addr = testbench.RemoteIPv6 + addr = dut.Net.RemoteIPv6 } - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(addr)) + boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, addr) defer dut.Close(t, boundFD) var conn udpConn var localAddr unix.Sockaddr if isIPv4 { - v4Conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + v4Conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) localAddr = v4Conn.LocalAddr(t) conn = &v4Conn } else { - v6Conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - localAddr = v6Conn.LocalAddr(t) + v6Conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) + localAddr = v6Conn.LocalAddr(t, dut.Net.RemoteDevID) conn = &v6Conn } defer conn.Close(t) diff --git a/test/perf/BUILD b/test/perf/BUILD index b763be50e..e25f090ae 100644 --- a/test/perf/BUILD +++ b/test/perf/BUILD @@ -1,3 +1,4 @@ +load("//tools:defs.bzl", "more_shards") load("//test/runner:defs.bzl", "syscall_test") package(licenses = ["notice"]) @@ -37,7 +38,7 @@ syscall_test( syscall_test( size = "enormous", debug = False, - shard_count = 10, + shard_count = more_shards, tags = ["nogotsan"], test = "//test/perf/linux:getdents_benchmark", ) diff --git a/test/root/BUILD b/test/root/BUILD index a9130b34f..8d9fff578 100644 --- a/test/root/BUILD +++ b/test/root/BUILD @@ -1,5 +1,4 @@ load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/vm:defs.bzl", "vm_test") package(licenses = ["notice"]) @@ -24,12 +23,8 @@ go_test( ], library = ":root", tags = [ - # Requires docker and runsc to be configured before the test runs. - # Also, the test needs to be run as root. Note that below, the - # root_vm_test relies on the default runtime 'runsc' being installed by - # the default installer. - "manual", "local", + "manual", ], visibility = ["//:sandbox"], deps = [ @@ -46,10 +41,3 @@ go_test( "@org_golang_x_sys//unix:go_default_library", ], ) - -vm_test( - name = "root_vm_test", - size = "large", - shard_count = 1, - targets = [":root_test"], -) diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go index 11ac5cb52..df52dd381 100644 --- a/test/root/crictl_test.go +++ b/test/root/crictl_test.go @@ -315,7 +315,7 @@ const ( // v1 is the containerd API v1. v1 string = "v1" - // v1 is the containerd API v21. + // v2 is the containerd API v2. v2 string = "v2" ) @@ -480,7 +480,7 @@ func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) { } // Wait for containerd to boot. - if err := testutil.WaitUntilRead(startupR, "Start streaming server", nil, 10*time.Second); err != nil { + if err := testutil.WaitUntilRead(startupR, "Start streaming server", 10*time.Second); err != nil { t.Fatalf("failed to start containerd: %v", err) } diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 7618f6a21..829247657 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -12,7 +12,7 @@ def _runner_test_impl(ctx): " mkdir -p \"${TEST_UNDECLARED_OUTPUTS_DIR}\"", " chmod a+rwx \"${TEST_UNDECLARED_OUTPUTS_DIR}\"", "fi", - "exec %s %s %s\n" % ( + "exec %s %s \"$@\" %s\n" % ( ctx.files.runner[0].short_path, " ".join(ctx.attr.runner_args), ctx.files.test[0].short_path, @@ -52,8 +52,6 @@ _runner_test = rule( def _syscall_test( test, - shard_count, - size, platform, use_tmpfs, tags, @@ -63,7 +61,8 @@ def _syscall_test( overlay = False, add_uds_tree = False, vfs2 = False, - fuse = False): + fuse = False, + **kwargs): # Prepend "runsc" to non-native platform names. full_platform = platform if platform == "native" else "runsc_" + platform @@ -126,15 +125,12 @@ def _syscall_test( name = name, test = test, runner_args = runner_args, - size = size, tags = tags, - shard_count = shard_count, + **kwargs ) def syscall_test( test, - shard_count = 5, - size = "small", use_tmpfs = False, add_overlay = False, add_uds_tree = False, @@ -142,18 +138,21 @@ def syscall_test( vfs2 = True, fuse = False, debug = True, - tags = None): + tags = None, + **kwargs): """syscall_test is a macro that will create targets for all platforms. Args: test: the test target. - shard_count: shards for defined tests. - size: the defined test size. use_tmpfs: use tmpfs in the defined tests. add_overlay: add an overlay test. add_uds_tree: add a UDS test. add_hostinet: add a hostinet test. + vfs2: enable VFS2 support. + fuse: enable FUSE support. + debug: enable debug output. tags: starting test tags. + **kwargs: additional test arguments. """ if not tags: tags = [] @@ -173,8 +172,6 @@ def syscall_test( _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, @@ -182,6 +179,7 @@ def syscall_test( debug = debug, vfs2 = True, fuse = fuse, + **kwargs ) if fuse: # Only generate *_vfs2_fuse target if fuse parameter is enabled. @@ -189,38 +187,35 @@ def syscall_test( _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = "native", use_tmpfs = False, add_uds_tree = add_uds_tree, tags = list(tags), debug = debug, + **kwargs ) for (platform, platform_tags) in platforms.items(): _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platform_tags + tags, debug = debug, + **kwargs ) if add_overlay: _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, debug = debug, overlay = True, + **kwargs ) # TODO(gvisor.dev/issue/4407): Remove tags to enable VFS2 overlay tests. @@ -230,8 +225,6 @@ def syscall_test( overlay_vfs2_tags.append("notap") _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, @@ -239,38 +232,35 @@ def syscall_test( debug = debug, overlay = True, vfs2 = True, + **kwargs ) if add_hostinet: _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, network = "host", add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, debug = debug, + **kwargs ) if not use_tmpfs: # Also test shared gofer access. _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, tags = platforms[default_platform] + tags, debug = debug, file_access = "shared", + **kwargs ) _syscall_test( test = test, - shard_count = shard_count, - size = size, platform = default_platform, use_tmpfs = use_tmpfs, add_uds_tree = add_uds_tree, @@ -278,4 +268,5 @@ def syscall_test( debug = debug, file_access = "shared", vfs2 = True, + **kwargs ) diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD index 22b526f59..510ffe013 100644 --- a/test/runtimes/BUILD +++ b/test/runtimes/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "bzl_library") +load("//tools:defs.bzl", "bzl_library", "more_shards", "most_shards") load("//test/runtimes:defs.bzl", "runtime_test") package(licenses = ["notice"]) @@ -7,7 +7,7 @@ runtime_test( name = "go1.12", exclude_file = "exclude/go1.12.csv", lang = "go", - shard_count = 8, + shard_count = more_shards, ) runtime_test( @@ -15,28 +15,28 @@ runtime_test( batch = 100, exclude_file = "exclude/java11.csv", lang = "java", - shard_count = 16, + shard_count = most_shards, ) runtime_test( name = "nodejs12.4.0", exclude_file = "exclude/nodejs12.4.0.csv", lang = "nodejs", - shard_count = 8, + shard_count = most_shards, ) runtime_test( name = "php7.3.6", exclude_file = "exclude/php7.3.6.csv", lang = "php", - shard_count = 8, + shard_count = more_shards, ) runtime_test( name = "python3.7.3", exclude_file = "exclude/python3.7.3.csv", lang = "python", - shard_count = 8, + shard_count = more_shards, ) bzl_library( diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go index 64e6e14db..9272137ff 100644 --- a/test/runtimes/runner/lib/lib.go +++ b/test/runtimes/runner/lib/lib.go @@ -34,12 +34,7 @@ import ( // 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, partitionNum, totalPartitions, batchSize int, timeout time.Duration) int { - if partitionNum <= 0 || totalPartitions <= 0 || partitionNum > totalPartitions { - fmt.Fprintf(os.Stderr, "invalid partition %d of %d", partitionNum, totalPartitions) - return 1 - } - +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. @@ -63,7 +58,7 @@ func RunTests(lang, image, excludeFile string, partitionNum, totalPartitions, ba // 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, partitionNum, totalPartitions, batchSize, timeout, excludes) + tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err.Error()) return 1 @@ -74,7 +69,7 @@ func RunTests(lang, image, excludeFile string, partitionNum, totalPartitions, ba } // getTests executes all tests as table tests. -func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, partitionNum, totalPartitions, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) { +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), @@ -90,18 +85,9 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, return nil, fmt.Errorf("docker exec failed: %v", err) } - // Calculate a subset of tests to run corresponding to the current - // shard. + // Calculate a subset of tests. tests := strings.Fields(list) sort.Strings(tests) - - partitionSize := len(tests) / totalPartitions - if partitionNum == totalPartitions { - tests = tests[(partitionNum-1)*partitionSize:] - } else { - tests = tests[(partitionNum-1)*partitionSize : partitionNum*partitionSize] - } - indices, err := testutil.TestIndicesForShard(len(tests)) if err != nil { return nil, fmt.Errorf("TestsForShard() failed: %v", err) @@ -122,6 +108,10 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, } tcs = append(tcs, tests[tc]) } + if len(tcs) == 0 { + // No tests to add to this batch. + continue + } itests = append(itests, testing.InternalTest{ Name: strings.Join(tcs, ", "), F: func(t *testing.T) { diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go index 5b3443e36..ec79a22c2 100644 --- a/test/runtimes/runner/main.go +++ b/test/runtimes/runner/main.go @@ -25,13 +25,11 @@ import ( ) 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") - partition = flag.Int("partition", 1, "partition number, this is 1-indexed") - totalPartitions = flag.Int("total_partitions", 1, "total number of partitions") - batchSize = flag.Int("batch", 50, "number of test cases run in one command") - timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout") + 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() { @@ -40,5 +38,5 @@ func main() { fmt.Fprintf(os.Stderr, "lang and image flags must not be empty\n") os.Exit(1) } - os.Exit(lib.RunTests(*lang, *image, *excludeFile, *partition, *totalPartitions, *batchSize, *timeout)) + os.Exit(lib.RunTests(*lang, *image, *excludeFile, *batchSize, *timeout)) } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index b5a4ef4df..a5b9233f7 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -1,3 +1,4 @@ +load("//tools:defs.bzl", "more_shards", "most_shards") load("//test/runner:defs.bzl", "syscall_test") package(licenses = ["notice"]) @@ -12,7 +13,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:accept_bind_test", ) @@ -32,7 +33,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:alarm_test", ) @@ -66,7 +67,7 @@ syscall_test( size = "large", # Produce too many logs in the debug mode. debug = False, - shard_count = 50, + shard_count = most_shards, # Takes too long for TSAN. Since this is kind of a stress test that doesn't # involve much concurrency, TSAN's usefulness here is limited anyway. tags = ["nogotsan"], @@ -211,7 +212,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:futex_test", ) @@ -258,7 +259,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:itimer_test", ) @@ -313,7 +314,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:mmap_test", ) @@ -347,6 +348,7 @@ syscall_test( syscall_test( add_overlay = True, + shard_count = more_shards, test = "//test/syscalls/linux:open_test", ) @@ -376,7 +378,7 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:pipe_test", ) @@ -448,7 +450,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:pty_test", ) @@ -475,6 +477,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:raw_socket_test", ) @@ -490,7 +493,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:readv_socket_test", ) @@ -539,7 +542,7 @@ syscall_test( ) syscall_test( - shard_count = 20, + shard_count = more_shards, test = "//test/syscalls/linux:semaphore_test", ) @@ -594,7 +597,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_abstract_test", ) @@ -605,7 +608,7 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_domain_test", ) @@ -618,19 +621,19 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_filesystem_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_inet_loopback_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, # Takes too long for TSAN. Creates a lot of TCP sockets. tags = ["nogotsan"], test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test", @@ -638,35 +641,38 @@ syscall_test( syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test", ) syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_loopback_test", ) syscall_test( size = "medium", - shard_count = 50, + add_hostinet = True, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test", ) syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_udp_loopback_test", ) @@ -677,6 +683,8 @@ syscall_test( syscall_test( size = "medium", + add_hostinet = True, + shard_count = more_shards, # Takes too long under gotsan to run. tags = ["nogotsan"], test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_nogotsan_test", @@ -691,6 +699,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:socket_ip_unbound_test", ) @@ -723,6 +732,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:socket_non_stream_blocking_local_test", ) @@ -753,7 +763,7 @@ syscall_test( syscall_test( # NOTE(b/116636318): Large sendmsg may stall a long time. size = "enormous", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_dgram_local_test", ) @@ -765,14 +775,14 @@ syscall_test( syscall_test( size = "large", add_overlay = True, - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_unix_pair_test", ) syscall_test( # NOTE(b/116636318): Large sendmsg may stall a long time. size = "enormous", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_seqpacket_local_test", ) @@ -798,13 +808,13 @@ syscall_test( syscall_test( size = "medium", - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test", ) syscall_test( size = "large", - shard_count = 50, + shard_count = most_shards, test = "//test/syscalls/linux:socket_unix_unbound_stream_test", ) @@ -858,7 +868,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:tcp_socket_test", ) @@ -867,6 +877,7 @@ syscall_test( ) syscall_test( + shard_count = more_shards, test = "//test/syscalls/linux:timerfd_test", ) @@ -897,13 +908,14 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:udp_bind_test", ) syscall_test( size = "medium", add_hostinet = True, - shard_count = 10, + shard_count = more_shards, test = "//test/syscalls/linux:udp_socket_test", ) @@ -947,7 +959,7 @@ syscall_test( syscall_test( size = "medium", - shard_count = 5, + shard_count = more_shards, test = "//test/syscalls/linux:wait_test", ) @@ -961,6 +973,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:proc_net_tcp_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 50baafbf7..760456a98 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -432,6 +432,9 @@ cc_binary( testonly = 1, srcs = ["chown.cc"], linkstatic = 1, + # We require additional UIDs for this test, so don't include the bazel + # sandbox as standard. + tags = ["no-sandbox"], deps = [ "//test/util:capability_util", "//test/util:file_descriptor", @@ -618,10 +621,7 @@ cc_binary( cc_binary( name = "exceptions_test", testonly = 1, - srcs = select_arch( - amd64 = ["exceptions.cc"], - arm64 = [], - ), + srcs = ["exceptions.cc"], linkstatic = 1, deps = [ gtest, @@ -796,8 +796,8 @@ cc_binary( deps = [ ":socket_test_util", "//test/util:cleanup", - "//test/util:epoll_util", "//test/util:eventfd_util", + "//test/util:file_descriptor", "//test/util:fs_util", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/flags:flag", @@ -808,6 +808,7 @@ cc_binary( "//test/util:multiprocess_util", "//test/util:posix_error", "//test/util:save_util", + "//test/util:signal_util", "//test/util:temp_path", "//test/util:test_util", "//test/util:thread_util", @@ -2450,6 +2451,27 @@ cc_library( ) cc_library( + name = "socket_ipv6_udp_unbound_test_cases", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound.cc", + ], + hdrs = [ + "socket_ipv6_udp_unbound.h", + ], + deps = [ + ":ip_socket_test_util", + ":socket_test_util", + "@com_google_absl//absl/memory", + gtest, + "//test/util:posix_error", + "//test/util:save_util", + "//test/util:test_util", + ], + alwayslink = 1, +) + +cc_library( name = "socket_ipv4_udp_unbound_netlink_test_cases", testonly = 1, srcs = [ @@ -2789,6 +2811,22 @@ cc_binary( ) cc_binary( + name = "socket_ipv6_udp_unbound_loopback_test", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound_loopback.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv6_udp_unbound_test_cases", + ":socket_test_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "socket_ipv4_udp_unbound_loopback_nogotsan_test", testonly = 1, srcs = [ diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc index 7a28b674d..5530ad18f 100644 --- a/test/syscalls/linux/chown.cc +++ b/test/syscalls/linux/chown.cc @@ -75,7 +75,16 @@ TEST_P(ChownParamTest, ChownFileSucceeds) { if (num_groups > 0) { std::vector<gid_t> list(num_groups); EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds()); - gid = list[0]; + // Scan the list of groups for a valid gid. Note that if a group is not + // defined in this local user namespace, then we will see 65534, and the + // group will not chown below as expected. So only change if we find a + // valid group in this list. + for (const gid_t other_gid : list) { + if (other_gid != 65534) { + gid = other_gid; + break; + } + } } EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid)); @@ -90,6 +99,7 @@ TEST_P(ChownParamTest, ChownFilePermissionDenied) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777)); + EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); // Drop privileges and change IDs only in child thread, or else this parent // thread won't be able to open some log files after the test ends. @@ -119,6 +129,7 @@ TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID)))); const std::string filename = NewTempAbsPath(); + EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds()); absl::Notification fileCreated, fileChowned; // Change UID only in child thread, or else this parent thread won't be able diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc index 420b9543f..11dc1c651 100644 --- a/test/syscalls/linux/exceptions.cc +++ b/test/syscalls/linux/exceptions.cc @@ -23,6 +23,7 @@ namespace gvisor { namespace testing { +#if defined(__x86_64__) // Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5 // "x87 FPU Control Word". constexpr uint16_t kX87ControlWordDefault = 0x37f; @@ -93,6 +94,9 @@ void InIOHelper(int width, int value) { }, ::testing::KilledBySignal(SIGSEGV), ""); } +#elif defined(__aarch64__) +void inline Halt() { asm("hlt #0\r\n"); } +#endif TEST(ExceptionTest, Halt) { // In order to prevent the regular handler from messing with things (and @@ -102,9 +106,14 @@ TEST(ExceptionTest, Halt) { sa.sa_handler = SIG_DFL; auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa)); +#if defined(__x86_64__) EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), ""); +#elif defined(__aarch64__) + EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGILL), ""); +#endif } +#if defined(__x86_64__) TEST(ExceptionTest, DivideByZero) { // See above. struct sigaction sa = {}; @@ -362,6 +371,7 @@ TEST(ExceptionTest, Int3Compact) { EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), ""); } +#endif } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc index 34016d4bd..4b581045b 100644 --- a/test/syscalls/linux/fcntl.cc +++ b/test/syscalls/linux/fcntl.cc @@ -14,10 +14,13 @@ #include <fcntl.h> #include <signal.h> +#include <sys/epoll.h> #include <sys/types.h> #include <syscall.h> #include <unistd.h> +#include <atomic> +#include <deque> #include <iostream> #include <list> #include <string> @@ -34,25 +37,27 @@ #include "test/syscalls/linux/socket_test_util.h" #include "test/util/cleanup.h" #include "test/util/eventfd_util.h" +#include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/save_util.h" +#include "test/util/signal_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" #include "test/util/timer_util.h" -ABSL_FLAG(std::string, child_setlock_on, "", +ABSL_FLAG(std::string, child_set_lock_on, "", "Contains the path to try to set a file lock on."); -ABSL_FLAG(bool, child_setlock_write, false, +ABSL_FLAG(bool, child_set_lock_write, false, "Whether to set a writable lock (otherwise readable)"); ABSL_FLAG(bool, blocking, false, "Whether to set a blocking lock (otherwise non-blocking)."); ABSL_FLAG(bool, retry_eintr, false, "Whether to retry in the subprocess on EINTR."); -ABSL_FLAG(uint64_t, child_setlock_start, 0, "The value of struct flock start"); -ABSL_FLAG(uint64_t, child_setlock_len, 0, "The value of struct flock len"); +ABSL_FLAG(uint64_t, child_set_lock_start, 0, "The value of struct flock start"); +ABSL_FLAG(uint64_t, child_set_lock_len, 0, "The value of struct flock len"); ABSL_FLAG(int32_t, socket_fd, -1, "A socket to use for communicating more state back " "to the parent."); @@ -60,6 +65,11 @@ ABSL_FLAG(int32_t, socket_fd, -1, namespace gvisor { namespace testing { +std::function<void(int, siginfo_t*, void*)> setsig_signal_handle; +void setsig_signal_handler(int signum, siginfo_t* siginfo, void* ucontext) { + setsig_signal_handle(signum, siginfo, ucontext); +} + class FcntlLockTest : public ::testing::Test { public: void SetUp() override { @@ -84,18 +94,93 @@ class FcntlLockTest : public ::testing::Test { int fds_[2] = {}; }; +struct SignalDelivery { + int num; + siginfo_t info; +}; + +class FcntlSignalTest : public ::testing::Test { + public: + void SetUp() override { + int pipe_fds[2]; + ASSERT_THAT(pipe2(pipe_fds, O_NONBLOCK), SyscallSucceeds()); + pipe_read_fd_ = pipe_fds[0]; + pipe_write_fd_ = pipe_fds[1]; + } + + PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { + struct sigaction handler; + handler.sa_sigaction = setsig_signal_handler; + setsig_signal_handle = [&](int signum, siginfo_t* siginfo, + void* unused_ucontext) { + SignalDelivery sig; + sig.num = signum; + sig.info = *siginfo; + signals_received_.push_back(sig); + num_signals_received_++; + }; + sigemptyset(&handler.sa_mask); + handler.sa_flags = SA_SIGINFO; + return ScopedSigaction(signum, handler); + } + + void FlushAndCloseFD(int fd) { + char buf; + int read_bytes; + do { + read_bytes = read(fd, &buf, 1); + } while (read_bytes > 0); + // read() can also fail with EWOULDBLOCK since the pipe is open in + // non-blocking mode. This is not an error. + EXPECT_TRUE(read_bytes == 0 || (read_bytes == -1 && errno == EWOULDBLOCK)); + EXPECT_THAT(close(fd), SyscallSucceeds()); + } + + void DupReadFD() { + ASSERT_THAT(pipe_read_fd_dup_ = dup(pipe_read_fd_), SyscallSucceeds()); + max_expected_signals++; + } + + void RegisterFD(int fd, int signum) { + ASSERT_THAT(fcntl(fd, F_SETOWN, getpid()), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd, F_SETSIG, signum), SyscallSucceeds()); + int old_flags; + ASSERT_THAT(old_flags = fcntl(fd, F_GETFL), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd, F_SETFL, old_flags | O_ASYNC), SyscallSucceeds()); + } + + void GenerateIOEvent() { + ASSERT_THAT(write(pipe_write_fd_, "test", 4), SyscallSucceedsWithValue(4)); + } + + void WaitForSignalDelivery(absl::Duration timeout) { + absl::Time wait_start = absl::Now(); + while (num_signals_received_ < max_expected_signals && + absl::Now() - wait_start < timeout) { + absl::SleepFor(absl::Milliseconds(10)); + } + } + + int pipe_read_fd_ = -1; + int pipe_read_fd_dup_ = -1; + int pipe_write_fd_ = -1; + int max_expected_signals = 1; + std::deque<SignalDelivery> signals_received_; + std::atomic<int> num_signals_received_ = 0; +}; + namespace { PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write, bool blocking, bool retry_eintr, int fd, off_t start, off_t length, pid_t* child) { std::vector<std::string> args = { - "/proc/self/exe", "--child_setlock_on", path, - "--child_setlock_start", absl::StrCat(start), "--child_setlock_len", - absl::StrCat(length), "--socket_fd", absl::StrCat(fd)}; + "/proc/self/exe", "--child_set_lock_on", path, + "--child_set_lock_start", absl::StrCat(start), "--child_set_lock_len", + absl::StrCat(length), "--socket_fd", absl::StrCat(fd)}; if (for_write) { - args.push_back("--child_setlock_write"); + args.push_back("--child_set_lock_write"); } if (blocking) { @@ -965,7 +1050,6 @@ TEST(FcntlTest, GetOwnNone) { // into F_{GET,SET}OWN_EX. EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } TEST(FcntlTest, GetOwnExNone) { @@ -1009,7 +1093,6 @@ TEST(FcntlTest, SetOwnPid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnPgrp) { @@ -1030,7 +1113,6 @@ TEST(FcntlTest, SetOwnPgrp) { SyscallSucceedsWithValue(0)); EXPECT_EQ(got_owner.type, F_OWNER_PGRP); EXPECT_EQ(got_owner.pid, pgid); - MaybeSave(); } TEST(FcntlTest, SetOwnUnset) { @@ -1058,7 +1140,6 @@ TEST(FcntlTest, SetOwnUnset) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } // F_SETOWN flips the sign of negative values, an operation that is guarded @@ -1130,7 +1211,6 @@ TEST(FcntlTest, SetOwnExTid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(owner.pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnExPid) { @@ -1146,7 +1226,6 @@ TEST(FcntlTest, SetOwnExPid) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(owner.pid)); - MaybeSave(); } TEST(FcntlTest, SetOwnExPgrp) { @@ -1168,7 +1247,6 @@ TEST(FcntlTest, SetOwnExPgrp) { SyscallSucceedsWithValue(0)); EXPECT_EQ(got_owner.type, set_owner.type); EXPECT_EQ(got_owner.pid, set_owner.pid); - MaybeSave(); } TEST(FcntlTest, SetOwnExUnset) { @@ -1201,7 +1279,6 @@ TEST(FcntlTest, SetOwnExUnset) { EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN), SyscallSucceedsWithValue(0)); - MaybeSave(); } TEST(FcntlTest, GetOwnExTid) { @@ -1258,9 +1335,269 @@ TEST(FcntlTest, GetOwnExPgrp) { EXPECT_EQ(got_owner.pid, set_owner.pid); } +TEST(FcntlTest, SetSig) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceedsWithValue(0)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGUSR1)); +} + +TEST(FcntlTest, SetSigDefaultsToZero) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + // Defaults to returning the zero value, indicating default behavior (SIGIO). + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigToDefault) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGIO), + SyscallSucceedsWithValue(0)); + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGIO)); + + // Can be reset to the default behavior. + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, 0), + SyscallSucceedsWithValue(0)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigInvalid) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(0)); +} + +TEST(FcntlTest, SetSigInvalidDoesNotResetPreviousChoice) { + FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); + + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceedsWithValue(0)); + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG), + SyscallSucceedsWithValue(SIGUSR1)); +} + +TEST_F(FcntlSignalTest, SetSigDefault) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + RegisterFD(pipe_read_fd_, 0); // Zero = default behavior + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGIO); + EXPECT_EQ(sig.info.si_signo, SIGIO); + // siginfo contents is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigCustom) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigUnregisterStillGetsSigio) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + RegisterFD(pipe_read_fd_, 0); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + signals_received_.pop_front(); + EXPECT_EQ(sig.num, SIGIO); + // siginfo contents is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigWithSigioStillGetsSiginfo) { + const auto signal_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + RegisterFD(pipe_read_fd_, SIGIO); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + EXPECT_EQ(sig.num, SIGIO); + EXPECT_EQ(sig.info.si_signo, SIGIO); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupThenCloseOld) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + FlushAndCloseFD(pipe_read_fd_); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **old** FD (even though it is closed). + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupThenCloseNew) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + FlushAndCloseFD(pipe_read_fd_dup_); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the old FD. + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupOldRegistered) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the old FD. + EXPECT_EQ(sig.num, SIGUSR1); + EXPECT_EQ(sig.info.si_signo, SIGUSR1); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupNewRegistered) { + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the new FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_dup_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupBothRegistered) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **new** signal number, but the **old** FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupBothRegisteredAfterDup) { + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + DupReadFD(); + RegisterFD(pipe_read_fd_, SIGUSR1); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with the **new** signal number, but the **old** FD. + EXPECT_EQ(sig.num, SIGUSR2); + EXPECT_EQ(sig.info.si_signo, SIGUSR2); + EXPECT_EQ(sig.info.si_fd, pipe_read_fd_); + EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM); +} + +TEST_F(FcntlSignalTest, SetSigDupUnregisterOld) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + RegisterFD(pipe_read_fd_, 0); // Should go back to SIGIO behavior. + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with SIGIO. + EXPECT_EQ(sig.num, SIGIO); + // siginfo is undefined in this case. +} + +TEST_F(FcntlSignalTest, SetSigDupUnregisterNew) { + const auto sigio_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO)); + const auto sigusr1_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1)); + const auto sigusr2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2)); + RegisterFD(pipe_read_fd_, SIGUSR1); + DupReadFD(); + RegisterFD(pipe_read_fd_dup_, SIGUSR2); + RegisterFD(pipe_read_fd_dup_, 0); // Should go back to SIGIO behavior. + GenerateIOEvent(); + WaitForSignalDelivery(absl::Seconds(1)); + ASSERT_EQ(num_signals_received_, 1); + SignalDelivery sig = signals_received_.front(); + // We get a signal with SIGIO. + EXPECT_EQ(sig.num, SIGIO); + // siginfo is undefined in this case. +} + // Make sure that making multiple concurrent changes to async signal generation // does not cause any race issues. -TEST(FcntlTest, SetFlSetOwnDoNotRace) { +TEST(FcntlTest, SetFlSetOwnSetSigDoNotRace) { FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE( Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); @@ -1268,32 +1605,40 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) { EXPECT_THAT(pid = getpid(), SyscallSucceeds()); constexpr absl::Duration runtime = absl::Milliseconds(300); - auto setAsync = [&s, &runtime] { + auto set_async = [&s, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, O_ASYNC), SyscallSucceeds()); sched_yield(); } }; - auto resetAsync = [&s, &runtime] { + auto reset_async = [&s, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, 0), SyscallSucceeds()); sched_yield(); } }; - auto setOwn = [&s, &pid, &runtime] { + auto set_own = [&s, &pid, &runtime] { for (auto start = absl::Now(); absl::Now() - start < runtime;) { ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid), SyscallSucceeds()); sched_yield(); } }; + auto set_sig = [&s, &runtime] { + for (auto start = absl::Now(); absl::Now() - start < runtime;) { + ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1), + SyscallSucceeds()); + sched_yield(); + } + }; std::list<ScopedThread> threads; for (int i = 0; i < 10; i++) { - threads.emplace_back(setAsync); - threads.emplace_back(resetAsync); - threads.emplace_back(setOwn); + threads.emplace_back(set_async); + threads.emplace_back(reset_async); + threads.emplace_back(set_own); + threads.emplace_back(set_sig); } } @@ -1302,57 +1647,60 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) { } // namespace testing } // namespace gvisor -int main(int argc, char** argv) { - gvisor::testing::TestInit(&argc, &argv); - - const std::string setlock_on = absl::GetFlag(FLAGS_child_setlock_on); - if (!setlock_on.empty()) { - int socket_fd = absl::GetFlag(FLAGS_socket_fd); - int fd = open(setlock_on.c_str(), O_RDWR, 0666); - if (fd == -1 && errno != 0) { - int err = errno; - std::cerr << "CHILD open " << setlock_on << " failed " << err - << std::endl; - exit(err); - } +int set_lock() { + const std::string set_lock_on = absl::GetFlag(FLAGS_child_set_lock_on); + int socket_fd = absl::GetFlag(FLAGS_socket_fd); + int fd = open(set_lock_on.c_str(), O_RDWR, 0666); + if (fd == -1 && errno != 0) { + int err = errno; + std::cerr << "CHILD open " << set_lock_on << " failed: " << err + << std::endl; + return err; + } - struct flock fl; - if (absl::GetFlag(FLAGS_child_setlock_write)) { - fl.l_type = F_WRLCK; - } else { - fl.l_type = F_RDLCK; - } - fl.l_whence = SEEK_SET; - fl.l_start = absl::GetFlag(FLAGS_child_setlock_start); - fl.l_len = absl::GetFlag(FLAGS_child_setlock_len); + struct flock fl; + if (absl::GetFlag(FLAGS_child_set_lock_write)) { + fl.l_type = F_WRLCK; + } else { + fl.l_type = F_RDLCK; + } + fl.l_whence = SEEK_SET; + fl.l_start = absl::GetFlag(FLAGS_child_set_lock_start); + fl.l_len = absl::GetFlag(FLAGS_child_set_lock_len); + + // Test the fcntl. + int err = 0; + int ret = 0; + + gvisor::testing::MonotonicTimer timer; + timer.Start(); + do { + ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl); + } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR); + auto usec = absl::ToInt64Microseconds(timer.Duration()); + + if (ret == -1 && errno != 0) { + err = errno; + std::cerr << "CHILD lock " << set_lock_on << " failed " << err << std::endl; + } - // Test the fcntl. - int err = 0; - int ret = 0; + // If there is a socket fd let's send back the time in microseconds it took + // to execute this syscall. + if (socket_fd != -1) { + gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec), + sizeof(usec)); + close(socket_fd); + } - gvisor::testing::MonotonicTimer timer; - timer.Start(); - do { - ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl); - } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR); - auto usec = absl::ToInt64Microseconds(timer.Duration()); - - if (ret == -1 && errno != 0) { - err = errno; - std::cerr << "CHILD lock " << setlock_on << " failed " << err - << std::endl; - } + close(fd); + return err; +} - // If there is a socket fd let's send back the time in microseconds it took - // to execute this syscall. - if (socket_fd != -1) { - gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec), - sizeof(usec)); - close(socket_fd); - } +int main(int argc, char** argv) { + gvisor::testing::TestInit(&argc, &argv); - close(fd); - exit(err); + if (!absl::GetFlag(FLAGS_child_set_lock_on).empty()) { + exit(set_lock()); } return gvisor::testing::RunAllTests(); diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc index db29bd59c..5d1735853 100644 --- a/test/syscalls/linux/kill.cc +++ b/test/syscalls/linux/kill.cc @@ -58,6 +58,12 @@ void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); } // If pid equals -1, then sig is sent to every process for which the calling // process has permission to send signals, except for process 1 (init). TEST(KillTest, CanKillAllPIDs) { + // If we're not running inside the sandbox, then we skip this test + // as our namespace may contain may more processes that cannot tolerate + // the signal below. We also cannot reliably create a new pid namespace + // for ourselves and test the same functionality. + SKIP_IF(!IsRunningOnGvisor()); + int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_fd(pipe_fds[0]); diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc index 78c36f98f..9d63782fb 100644 --- a/test/syscalls/linux/open_create.cc +++ b/test/syscalls/linux/open_create.cc @@ -112,14 +112,6 @@ TEST(CreateTest, CreatFileWithOTruncAndReadOnly) { ASSERT_THAT(close(dirfd), SyscallSucceeds()); } -TEST(CreateTest, CreateFailsOnUnpermittedDir) { - // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to - // always override directory permissions. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); - ASSERT_THAT(open("/foo", O_CREAT | O_RDWR, 0644), - SyscallFailsWithErrno(EACCES)); -} - TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) { // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to // always override directory permissions. diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 7a0f33dff..575be014c 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -17,6 +17,7 @@ #include <fcntl.h> #include <limits.h> #include <linux/magic.h> +#include <linux/sem.h> #include <sched.h> #include <signal.h> #include <stddef.h> @@ -2409,6 +2410,28 @@ TEST(ProcFilesystems, PresenceOfShmMaxMniAll) { ASSERT_LE(shmall, ULONG_MAX - (1UL << 24)); } +TEST(ProcFilesystems, PresenceOfSem) { + uint32_t semmsl = 0; + uint32_t semmns = 0; + uint32_t semopm = 0; + uint32_t semmni = 0; + std::string proc_file; + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem")); + ASSERT_FALSE(proc_file.empty()); + std::vector<absl::string_view> sem_limits = + absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace()); + ASSERT_EQ(sem_limits.size(), 4); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni)); + + ASSERT_EQ(semmsl, SEMMSL); + ASSERT_EQ(semmns, SEMMNS); + ASSERT_EQ(semopm, SEMOPM); + ASSERT_EQ(semmni, SEMMNI); +} + // Check that /proc/mounts is a symlink to self/mounts. TEST(ProcMounts, IsSymlink) { auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts")); @@ -2459,7 +2482,7 @@ void CheckDuplicatesRecursively(std::string path) { return; } auto dir_closer = Cleanup([&dir]() { closedir(dir); }); - std::unordered_set<std::string> children; + absl::node_hash_set<std::string> children; while (true) { // Readdir(3): If the end of the directory stream is reached, NULL is // returned and errno is not changed. If an error occurs, NULL is @@ -2478,6 +2501,10 @@ void CheckDuplicatesRecursively(std::string path) { absl::EndsWith(path, "/net")) { break; } + // We may also see permission failures traversing some files. + if (errno == EACCES && absl::StartsWith(path, "/proc/")) { + break; + } // Otherwise, no errors are allowed. ASSERT_EQ(errno, 0) << path; diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index 54709371c..955bcee4b 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -852,6 +852,51 @@ TEST(RawSocketTest, IPv6ProtoRaw) { SyscallFailsWithErrno(EINVAL)); } +TEST(RawSocketTest, IPv6SendMsg) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + char kBuf[] = "hello"; + struct iovec iov = {}; + iov.iov_base = static_cast<void*>(const_cast<char*>(kBuf)); + iov.iov_len = static_cast<size_t>(sizeof(kBuf)); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct msghdr msg = {}; + msg.msg_name = static_cast<void*>(&addr); + msg.msg_namelen = sizeof(sockaddr_in); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallFailsWithErrno(EINVAL)); +} + +TEST_P(RawSocketTest, ConnectOnIPv6Socket) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + ASSERT_THAT(connect(sock, reinterpret_cast<struct sockaddr*>(&addr), + sizeof(sockaddr_in6)), + SyscallFailsWithErrno(EAFNOSUPPORT)); +} + INSTANTIATE_TEST_SUITE_P( AllInetTests, RawSocketTest, ::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP), diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index 890f4a246..c2f080917 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -20,6 +20,7 @@ #include <atomic> #include <cerrno> #include <ctime> +#include <set> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -31,10 +32,23 @@ #include "test/util/test_util.h" #include "test/util/thread_util.h" +using ::testing::Contains; + 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) {} @@ -773,6 +787,154 @@ TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) { EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0); } +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)); + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceedsWithValue(0)); + 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()); + + int index_count = 0; + 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_id != -1) { + ASSERT_THAT(sem_ids, Contains(sem_id)); + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + index_count += 1; + } + } + EXPECT_EQ(index_count, kLoops); + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), + SyscallSucceedsWithValue(max_used_index)); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } + + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceedsWithValue(0)); + 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)); + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceedsWithValue(0)); + 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); + EXPECT_EQ(info.semusz, sem_ids.size()); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_EQ(info.semaem, sem_ids.size() * kSemSetSize); + + int index_count = 0; + 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_id != -1) { + ASSERT_THAT(sem_ids, Contains(sem_id)); + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + index_count += 1; + } + } + EXPECT_EQ(index_count, kLoops); + ASSERT_THAT(semctl(0, 0, SEM_INFO, &info), + SyscallSucceedsWithValue(max_used_index)); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } + + ASSERT_THAT(semctl(0, 0, SEM_INFO, &info), SyscallSucceedsWithValue(0)); + 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, 0); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_EQ(info.semaem, 0); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/signalfd.cc b/test/syscalls/linux/signalfd.cc index 389e5fca2..c86cd2755 100644 --- a/test/syscalls/linux/signalfd.cc +++ b/test/syscalls/linux/signalfd.cc @@ -126,7 +126,7 @@ TEST_P(SignalfdTest, Blocking) { // Shared tid variable. absl::Mutex mu; - bool has_tid; + bool has_tid = false; pid_t tid; // Start a thread reading. diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc index 796546224..a28ee2233 100644 --- a/test/syscalls/linux/socket_generic.cc +++ b/test/syscalls/linux/socket_generic.cc @@ -818,32 +818,55 @@ TEST_P(AllSocketPairTest, GetSockoptProtocol) { } } -TEST_P(AllSocketPairTest, GetSockoptBroadcast) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int opt = -1; - socklen_t optlen = sizeof(opt); - EXPECT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_BROADCAST, &opt, &optlen), - SyscallSucceeds()); - ASSERT_EQ(optlen, sizeof(opt)); - EXPECT_EQ(opt, 0); +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, SetAndGetSocketBroadcastOption) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); - int kSockOptOn = 1; - ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_BROADCAST, - &kSockOptOn, sizeof(kSockOptOn)), - SyscallSucceedsWithValue(0)); +TEST_P(AllSocketPairTest, GetSocketOutOfBandInlineOption) { + // We do not support disabling this option. It is always enabled. + SKIP_IF(!IsRunningOnGvisor()); - int got = -1; - socklen_t length = sizeof(got); - ASSERT_THAT( - getsockopt(sockets->first_fd(), SOL_SOCKET, SO_BROADCAST, &got, &length), - SyscallSucceedsWithValue(0)); + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int enable = -1; + socklen_t enableLen = sizeof(enable); - ASSERT_EQ(length, sizeof(got)); - EXPECT_EQ(got, kSockOptOn); + 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 diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index e19a83413..51b77ad85 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -1185,19 +1185,44 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen)); ASSERT_EQ(addrlen, listener.addr_len); - // TODO(gvisor.dev/issue/3812): Remove after SO_ERROR is fixed. - if (IsRunningOnGvisor()) { - char buf[10]; - ASSERT_THAT(ReadFd(accept_fd.get(), buf, sizeof(buf)), - SyscallFailsWithErrno(ECONNRESET)); - } else { + // Wait for accept_fd to process the RST. + const int kTimeout = 10000; + struct pollfd pfd = { + .fd = accept_fd.get(), + .events = POLLIN, + }; + ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1)); + ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR); + + { int err; socklen_t optlen = sizeof(err); ASSERT_THAT( getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), SyscallSucceeds()); + // This should return ECONNRESET as the socket just received a RST packet + // from the peer. + ASSERT_EQ(optlen, sizeof(err)); ASSERT_EQ(err, ECONNRESET); + } + { + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT( + getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + // This should return no error as the previous getsockopt call would have + // cleared the socket error. ASSERT_EQ(optlen, sizeof(err)); + ASSERT_EQ(err, 0); + } + { + sockaddr_storage peer_addr; + socklen_t addrlen = sizeof(peer_addr); + // The socket is not connected anymore and should return ENOTCONN. + ASSERT_THAT(getpeername(accept_fd.get(), + reinterpret_cast<sockaddr*>(&peer_addr), &addrlen), + SyscallFailsWithErrno(ENOTCONN)); } } @@ -2805,5 +2830,28 @@ INSTANTIATE_TEST_SUITE_P( } // namespace +// Check that loopback receives connections from any address in the range: +// 127.0.0.1 to 127.254.255.255. This behavior is exclusive to IPv4. +TEST_F(SocketInetLoopbackTest, LoopbackAddressRangeConnect) { + TestAddress const& listener = V4Any(); + + in_addr_t addresses[] = { + INADDR_LOOPBACK, + INADDR_LOOPBACK + 1, // 127.0.0.2 + (in_addr_t)0x7f000101, // 127.0.1.1 + (in_addr_t)0x7f010101, // 127.1.1.1 + (in_addr_t)0x7ffeffff, // 127.254.255.255 + }; + for (const auto& address : addresses) { + TestAddress connector("V4Loopback"); + connector.addr.ss_family = AF_INET; + connector.addr_len = sizeof(sockaddr_in); + reinterpret_cast<sockaddr_in*>(&connector.addr)->sin_addr.s_addr = + htonl(address); + + tcpSimpleConnectTest(listener, connector, true); + } +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index f69f8f99f..2fcd08112 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -15,6 +15,9 @@ #include "test/syscalls/linux/socket_ip_udp_generic.h" #include <errno.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ #include <netinet/in.h> #include <netinet/tcp.h> #include <poll.h> @@ -356,6 +359,58 @@ TEST_P(UDPSocketPairTest, SetAndGetIPPKTINFO) { EXPECT_EQ(get_len, sizeof(get)); } +// Test getsockopt for a socket which is not set with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, ReceiveOrigDstAddrDefault) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int get = -1; + socklen_t get_len = sizeof(get); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get_len, sizeof(get)); + EXPECT_EQ(get, kSockOptOff); +} + +// Test setsockopt and getsockopt for a socket with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, SetAndGetReceiveOrigDstAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + + // Check getsockopt before IP_PKTINFO is set. + int get = -1; + socklen_t get_len = sizeof(get); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOn); + EXPECT_EQ(get_len, sizeof(get)); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOff, + sizeof(kSockOptOff)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOff); + EXPECT_EQ(get_len, sizeof(get)); +} + // Holds TOS or TClass information for IPv4 or IPv6 respectively. struct RecvTosOption { int level; diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index b3f54e7f6..e557572a7 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -2222,6 +2222,90 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK)); } +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V4Loopback(); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check the data + sockaddr_in received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = reinterpret_cast<sockaddr_in*>(&receiver_addr.addr); + EXPECT_EQ(received_addr.sin_addr.s_addr, orig_receiver_addr->sin_addr.s_addr); + EXPECT_EQ(received_addr.sin_port, orig_receiver_addr->sin_port); +} + // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufBelowMin) { diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc new file mode 100644 index 000000000..08526468e --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc @@ -0,0 +1,131 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/syscalls/linux/socket_ipv6_udp_unbound.h" + +#include <arpa/inet.h> +#include <netinet/in.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <cstdio> +#include <cstring> + +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V6Loopback(); + int level = SOL_IPV6; + int type = IPV6_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in6)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in6))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in6); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check that the received address in the control message matches the expected + // receiver's address. + sockaddr_in6 received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = + reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr); + EXPECT_EQ(memcmp(&received_addr.sin6_addr, &orig_receiver_addr->sin6_addr, + sizeof(in6_addr)), + 0); + EXPECT_EQ(received_addr.sin6_port, orig_receiver_addr->sin6_port); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.h b/test/syscalls/linux/socket_ipv6_udp_unbound.h new file mode 100644 index 000000000..71e160f73 --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.h @@ -0,0 +1,29 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ + +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to IPv6 UDP sockets. +using IPv6UDPUnboundSocketTest = SimpleSocketTest; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc new file mode 100644 index 000000000..058336ecc --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc @@ -0,0 +1,32 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <vector> + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_ipv6_udp_unbound.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +INSTANTIATE_TEST_SUITE_P( + IPv6UDPSockets, IPv6UDPUnboundSocketTest, + ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket, + AllBitwiseCombinations(List<int>{ + 0, SOCK_NONBLOCK})))); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index bc2c8278c..714848b8e 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -964,37 +964,156 @@ TEST_P(TcpSocketTest, PollAfterShutdown) { SyscallSucceedsWithValue(1)); } -TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { +TEST_P(SimpleTcpSocketTest, NonBlockingConnectRetry) { + const FileDescriptor listener = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + // Initialize address to the loopback one. sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); socklen_t addrlen = sizeof(addr); - const FileDescriptor s = + // Bind to some port but don't listen yet. + ASSERT_THAT( + bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Get the address we're bound to, then connect to it. We need to do this + // because we're allowing the stack to pick a port for us. + ASSERT_THAT(getsockname(listener.get(), + reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + + FileDescriptor connector = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); - // Set the FD to O_NONBLOCK. - int opts; - ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); - opts |= O_NONBLOCK; - ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); + // Verify that connect fails. + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNREFUSED)); - ASSERT_THAT(RetryEINTR(connect)( + // Now start listening + ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds()); + + // TODO(gvisor.dev/issue/3828): Issuing connect() again on a socket that + // failed first connect should succeed. + if (IsRunningOnGvisor()) { + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); + return; + } + + // Verify that connect now succeeds. + ASSERT_THAT( + RetryEINTR(connect)(connector.get(), + reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Accept the connection. + const FileDescriptor accepted = + ASSERT_NO_ERRNO_AND_VALUE(Accept(listener.get(), nullptr, nullptr)); +} + +// nonBlockingConnectNoListener returns a socket on which a connect that is +// expected to fail has been issued. +PosixErrorOr<FileDescriptor> nonBlockingConnectNoListener(const int family, + sockaddr_storage addr, + socklen_t addrlen) { + // We will first create a socket and bind to ensure we bind a port but will + // not call listen on this socket. + // Then we will create a new socket that will connect to the port bound by + // the first socket and that shoud fail. + constexpr int sock_type = SOCK_STREAM | SOCK_NONBLOCK; + int b_sock; + RETURN_ERROR_IF_SYSCALL_FAIL(b_sock = socket(family, sock_type, IPPROTO_TCP)); + FileDescriptor b(b_sock); + EXPECT_THAT(bind(b.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + // Get the address bound by the listening socket. + EXPECT_THAT( + getsockname(b.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + + // Now create another socket and issue a connect on this one. This connect + // should fail as there is no listener. + int c_sock; + RETURN_ERROR_IF_SYSCALL_FAIL(c_sock = socket(family, sock_type, IPPROTO_TCP)); + FileDescriptor s(c_sock); + + // Now connect to the bound address and this should fail as nothing + // is listening on the bound address. + EXPECT_THAT(RetryEINTR(connect)( s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), SyscallFailsWithErrno(EINPROGRESS)); - // Now polling on the FD with a timeout should return 0 corresponding to no - // FDs ready. - struct pollfd poll_fd = {s.get(), POLLOUT, 0}; - EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000), - SyscallSucceedsWithValue(1)); + // Wait for the connect to fail. + struct pollfd poll_fd = {s.get(), POLLERR, 0}; + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1)); + return std::move(s); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); int err; socklen_t optlen = sizeof(err); ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), SyscallSucceeds()); - + ASSERT_THAT(optlen, sizeof(err)); EXPECT_EQ(err, ECONNREFUSED); + + unsigned char c; + ASSERT_THAT(read(s.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0)); + int opts; + EXPECT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds()); + opts &= ~O_NONBLOCK; + EXPECT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds()); + // Try connecting again. + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerRead) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); + + unsigned char c; + ASSERT_THAT(read(s.get(), &c, 1), SyscallFailsWithErrno(ECONNREFUSED)); + ASSERT_THAT(read(s.get(), &c, 1), SyscallSucceedsWithValue(0)); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); +} + +TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerPeek) { + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie(); + + unsigned char c; + ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), + SyscallFailsWithErrno(ECONNREFUSED)); + ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), SyscallSucceedsWithValue(0)); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallFailsWithErrno(ECONNABORTED)); } TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { @@ -1235,6 +1354,19 @@ TEST_P(SimpleTcpSocketTest, CleanupOnConnectionRefused) { // Attempt #2, with the new socket and reused addr our connect should fail in // the same way as before, not with an EADDRINUSE. + // + // TODO(gvisor.dev/issue/3828): 2nd connect on a socket which failed connect + // first time should succeed. + // gVisor never issues the second connect and returns ECONNABORTED instead. + // Linux actually sends a SYN again and gets a RST and correctly returns + // ECONNREFUSED. + if (IsRunningOnGvisor()) { + ASSERT_THAT(connect(client_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(ECONNABORTED)); + return; + } ASSERT_THAT(connect(client_s.get(), reinterpret_cast<const struct sockaddr*>(&bound_addr), bound_addrlen), diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index d65275fd3..90ef8bf21 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -374,6 +374,69 @@ TEST_P(UdpSocketTest, BindInUse) { SyscallFailsWithErrno(EADDRINUSE)); } +TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) { + // Discover a free unused port by creating a new UDP socket, binding it + // recording the just bound port and closing it. This is not guaranteed as it + // can still race with other port UDP sockets trying to bind a port at the + // same time. + struct sockaddr_storage addr_storage = InetLoopbackAddr(); + socklen_t addrlen = sizeof(addr_storage); + struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); + ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); + EXPECT_EQ(addrlen, addrlen_); + EXPECT_NE(*Port(&addr_storage), 0); + ASSERT_THAT(close(s.release()), SyscallSucceeds()); + + // Now connect to the port that we just released. This should generate an + // ECONNREFUSED error. + ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); + char buf[512]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. + ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), + SyscallSucceedsWithValue(sizeof(buf))); + + // Now verify that we got an ICMP error back of ECONNREFUSED. + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, ECONNREFUSED); + ASSERT_EQ(optlen, sizeof(err)); +} + +TEST_P(UdpSocketTest, ConnectSimultaneousWriteToInvalidPort) { + // Discover a free unused port by creating a new UDP socket, binding it + // recording the just bound port and closing it. This is not guaranteed as it + // can still race with other port UDP sockets trying to bind a port at the + // same time. + struct sockaddr_storage addr_storage = InetLoopbackAddr(); + socklen_t addrlen = sizeof(addr_storage); + struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); + ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); + EXPECT_EQ(addrlen, addrlen_); + EXPECT_NE(*Port(&addr_storage), 0); + ASSERT_THAT(close(s.release()), SyscallSucceeds()); + + // Now connect to the port that we just released. + ScopedThread t([&] { + ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); + }); + + char buf[512]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. + ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), + SyscallSucceedsWithValue(sizeof(buf))); + t.Join(); +} + TEST_P(UdpSocketTest, ReceiveAfterConnect) { ASSERT_NO_ERRNO(BindLoopback()); ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); |