diff options
Diffstat (limited to 'test/packetimpact/runner')
-rw-r--r-- | test/packetimpact/runner/BUILD | 1 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 20 | ||||
-rw-r--r-- | test/packetimpact/runner/dut.go | 421 |
3 files changed, 291 insertions, 151 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 494a7f96c..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,8 +254,6 @@ 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", @@ -257,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 ad1d73de2..ebbf17504 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) { - 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,161 @@ 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) + 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")) + + const containerDUTTestNetsDir = "/tmp/dut-test-nets" + const dutTestNetsFileName = "pool.json" + 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) + snifferProg := "tcpdump" + if tshark { + snifferProg = "tshark" + } + for _, n := range dutTestNets { + _, err := testbenchContainer.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 := 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() { + time.Sleep(1 * time.Second) + if logs, err := testbenchContainer.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 +307,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 +342,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) (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 +362,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) (net.IP, net.HardwareAddr, uint32, string) { +func (dut *DockerDUT) Prepare(ctx context.Context, t *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 +370,31 @@ 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) + 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(DutAddr, *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(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 { + 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 +403,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 +419,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 +429,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 +456,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 +521,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, 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 +} + +// 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)), + } +} |