diff options
author | Zeling Feng <zeling@google.com> | 2020-11-24 23:19:46 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-11-24 23:22:05 -0800 |
commit | d04144fbb7b9fcb055e3aa3c2246f9b498923cc0 (patch) | |
tree | e2f8fe30911f749204ef89b6aaa14f6251531c16 | |
parent | 99f2d0ea2f2ec7a9758584d53db64008be33fac4 (diff) |
[2/3] Support isolated containers for parallel packetimpact tests
Added a new flag num_duts to the test runner to create multiple DUTs for the
testbench can connect to.
PiperOrigin-RevId: 344195435
-rw-r--r-- | test/packetimpact/runner/BUILD | 1 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 14 | ||||
-rw-r--r-- | test/packetimpact/runner/dut.go | 395 | ||||
-rw-r--r-- | test/packetimpact/testbench/BUILD | 1 | ||||
-rw-r--r-- | test/packetimpact/testbench/testbench.go | 98 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 1 | ||||
-rw-r--r-- | test/packetimpact/tests/udp_recv_mcast_bcast_test.go | 2 |
7 files changed, 272 insertions, 240 deletions
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 60f0ebae3..66abba563 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 = [ diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index ad1d73de2..edadf4f01 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. // @@ -102,141 +110,146 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co } dockerutil.EnsureSupportedDockerVersion() - // 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. - ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet")) - testNet := dockerutil.NewNetwork(ctx, logger("testNet")) - for _, dn := range []*dockerutil.Network{ctrlNet, testNet} { - for { - if err := createDockerNetwork(ctx, dn); err != nil { - t.Log("creating docker network:", err) - const wait = 100 * time.Millisecond - t.Logf("sleeping %s and will try creating docker network again", wait) - // This can fail if another docker network claimed the same IP so we'll - // just try again. - time.Sleep(wait) - continue + var dockerNetworks []*dockerutil.Network + var dutTestNets []*testbench.DUTTestNet + var duts []DUT + + for i := 0; i < numDUTs; i++ { + // 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. + ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet")) + testNet := dockerutil.NewNetwork(ctx, logger("testNet")) + for _, dn := range []*dockerutil.Network{ctrlNet, testNet} { + for { + if err := createDockerNetwork(ctx, dn); err != nil { + t.Log("creating docker network:", err) + const wait = 100 * time.Millisecond + t.Logf("sleeping %s and will try creating docker network again", wait) + // This can fail if another docker network claimed the same IP so we + // will just try again. + time.Sleep(wait) + continue + } + break } - break - } - dn := dn - t.Cleanup(func() { - if err := dn.Cleanup(ctx); err != nil { - t.Errorf("unable to cleanup container %s: %s", dn.Name, err) + dn := dn + t.Cleanup(func() { + if err := dn.Cleanup(ctx); err != nil { + t.Errorf("unable to cleanup container %s: %s", dn.Name, err) + } + }) + // Sanity check. + if inspect, err := dn.Inspect(ctx); err != nil { + t.Fatalf("failed to inspect network %s: %v", dn.Name, err) + } else if inspect.Name != dn.Name { + t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name) } - }) - // Sanity check. - if inspect, err := dn.Inspect(ctx); err != nil { - t.Fatalf("failed to inspect network %s: %v", dn.Name, err) - } else if inspect.Name != dn.Name { - t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name) } - } + dockerNetworks = append(dockerNetworks, ctrlNet, testNet) - 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) + // Create the Docker container for the DUT. + var dut DUT + if native { + dut = mkDevice(dockerutil.MakeNativeContainer(ctx, logger(fmt.Sprintf("dut-%d", i)))) + } else { + dut = mkDevice(dockerutil.MakeContainer(ctx, logger(fmt.Sprintf("dut-%d", i)))) } - }) + duts = append(duts, dut) - const testOutputDir = "/tmp/testoutput" - - // Create the Docker container for the DUT. - var dut *dockerutil.Container - if native { - dut = dockerutil.MakeNativeContainer(ctx, logger("dut")) - } else { - dut = dockerutil.MakeContainer(ctx, logger("dut")) + runOpts := dockerutil.RunOpts{ + Image: "packetimpact", + CapAdd: []string{"NET_ADMIN"}, + } + mountTempDirectory(t, &runOpts, "dut-output", testOutputDir) + + remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev := dut.Prepare(ctx, t, runOpts, ctrlNet, testNet) + ipv4PrefixLength, _ := testNet.Subnet.Mask.Size() + dutTestNets = append(dutTestNets, &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, + }) } - t.Cleanup(func() { - dut.CleanUp(ctx) - }) + // Create the Docker container for the testbench. + testbench := dockerutil.MakeNativeContainer(ctx, logger("testbench")) + const containerDUTTestNetsDir = "/tmp/dut-test-nets" + const dutTestNetsFileName = "pool.json" runOpts := dockerutil.RunOpts{ Image: "packetimpact", CapAdd: []string{"NET_ADMIN"}, - Mounts: []mount.Mount{{ - Type: mount.TypeBind, - Source: tmpDir, - Target: testOutputDir, - ReadOnly: false, - }}, } - - device := mkDevice(dut) - remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev := device.Prepare(ctx, t, runOpts, ctrlNet, testNet) - - // Create the Docker container for the testbench. - testbench := dockerutil.MakeNativeContainer(ctx, logger("testbench")) - + mountTempDirectory(t, &runOpts, "testbench-output", testOutputDir) 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", - } - 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", - } - snifferRegex = "Capturing on.*\n" - } - if err := StartContainer( ctx, runOpts, testbench, 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, testbench, 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, testbench, name) + if err != nil { + t.Fatalf("failed to get IPV6 address on %s: %s", testbench.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) + snifferProg := "tcpdump" + if tshark { + snifferProg = "tshark" + } + for _, n := range dutTestNets { + _, err := testbench.ExecProcess(ctx, dockerutil.ExecOpts{}, snifferArgs(n.LocalDevName)...) + if err != nil { + t.Fatalf("failed to start exec a sniffer on %s: %s", n.LocalDevName, 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, 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", n.LocalDevName, "-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) + } } } + t.Cleanup(func() { + time.Sleep(1 * time.Second) + if logs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, "killall", snifferProg); 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 +261,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...) 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 @@ -311,11 +322,11 @@ func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockeru ctx, runOpts, dut.c, - DutAddr, + 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) } @@ -324,28 +335,14 @@ func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockeru t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err) } - dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DutAddr, *testNet.Subnet)) + dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DUTAddr, *testNet.Subnet)) if err != nil { t.Fatal(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(DutAddr, *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 { + t.Fatalf("failed to get IPv6 address on %s: %s", dut.c.Name, err) } const testNetDev = "eth2" @@ -358,11 +355,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,8 +371,8 @@ 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 for i := 0; i < 4; i++ { @@ -388,15 +381,25 @@ func AddressInSubnet(addr net.IP, subnet net.IPNet) net.IP { return net.IP(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 +408,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 +473,51 @@ 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 { + t.Helper() + tmpDir, err := ioutil.TempDir("", hostDirTemplate) + if err != nil { + t.Fatalf("failed to create a temp dir: %s", 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 +} + +// snifferArgs returns the correct command line to run sniffer on the testbench. +func snifferArgs(devName string) []string { + if tshark { + // Run tshark in the test bench unbuffered, without DNS resolution, just + // on the interface with the test packets. + return []string{ + "tshark", "-V", "-l", "-n", "-i", devName, + "-o", "tcp.check_checksum:TRUE", + "-o", "udp.check_checksum:TRUE", + } + } + // Run tcpdump in the test bench unbuffered, without DNS resolution, just + // on the interface with the test packets. + return []string{ + "tcpdump", + "-S", "-vvv", "-U", "-n", + "-i", devName, + "-w", filepath.Join(testOutputDir, fmt.Sprintf("%s.pcap", devName)), + } +} 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/testbench.go b/test/packetimpact/testbench/testbench.go index 92200add9..891897d55 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -17,15 +17,13 @@ package testbench import ( + "encoding/json" "flag" "fmt" "math/rand" "net" - "os/exec" "testing" "time" - - "gvisor.dev/gvisor/test/packetimpact/netdevs" ) var ( @@ -36,25 +34,12 @@ var ( // 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 - - // TODO(zeling): Remove the following variables once the test runner side is - // ready. - localDevice = "" - remoteDevice = "" - localIPv4 = "" - remoteIPv4 = "" - ipv4PrefixLength = 0 - localIPv6 = "" - remoteIPv6 = "" - localInterfaceID uint32 - remoteInterfaceID uint64 - localMAC = "" - remoteMAC = "" - posixServerIP = "" - posixServerPort = 40000 ) // DUTTestNet describes the test network setup on dut and how the testbench @@ -98,19 +83,10 @@ type DUTTestNet struct { // 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") - 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.Uint64Var(&remoteInterfaceID, "remote_interface_id", remoteInterfaceID, "remote interface ID for test packets") - 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(&dutTestNetsJSON, "dut_test_nets_json", dutTestNetsJSON, "path to the dut test nets json file") } // Initialize initializes the testbench, it parse the flags and sets up the @@ -118,61 +94,27 @@ func registerFlags(fs *flag.FlagSet) { func Initialize(fs *flag.FlagSet) { registerFlags(fs) flag.Parse() - if err := genPseudoFlags(); err != nil { - panic(err) - } - var dut DUTTestNet - var err error - dut.LocalMAC, err = net.ParseMAC(localMAC) - if err != nil { - panic(err) - } - dut.RemoteMAC, err = net.ParseMAC(remoteMAC) - if err != nil { + if err := loadDUTTestNets(); err != nil { panic(err) } - dut.LocalIPv4 = net.ParseIP(localIPv4).To4() - dut.LocalIPv6 = net.ParseIP(localIPv6).To16() - dut.RemoteIPv4 = net.ParseIP(remoteIPv4).To4() - dut.RemoteIPv6 = net.ParseIP(remoteIPv6).To16() - dut.LocalDevID = uint32(localInterfaceID) - dut.RemoteDevID = uint32(remoteInterfaceID) - dut.LocalDevName = localDevice - dut.RemoteDevName = remoteDevice - dut.POSIXServerIP = net.ParseIP(posixServerIP) - dut.POSIXServerPort = uint16(posixServerPort) - dut.IPv4PrefixLength = ipv4PrefixLength - - dutTestNets = make(chan *DUTTestNet, 1) - dutTestNets <- &dut } -// 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) +// 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) } - - _, deviceInfo, err := netdevs.FindDeviceByIP(net.ParseIP(localIPv4), devs) - if err != nil { - return fmt.Errorf("can't find deviceInfo: %w", err) + if got, want := len(parsedTestNets), 1; got < want { + return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want) } - - 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() + // 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 } diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 33bd070c1..ce21c079a 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -371,4 +371,5 @@ 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] diff --git a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go index 71cde6cde..b29c07825 100644 --- a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go +++ b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go @@ -44,7 +44,7 @@ func TestUDPRecvMcastBcast(t *testing.T) { {bound: subnetBcastAddr, to: subnetBcastAddr}, - // FIXME(gvisor.dev/issues/4896): Previously by the time subnetBcastAddr is + // 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 |