Automated rollback of changelist 311424257

PiperOrigin-RevId: 313300554
This commit is contained in:
gVisor bot
2020-05-26 17:40:57 -07:00
parent 6111950f89
commit 92bafd7929
14 changed files with 626 additions and 346 deletions
+109 -1
View File
@@ -148,6 +148,62 @@ func (m MountMode) String() string {
panic(fmt.Sprintf("invalid mode: %d", m))
}
// DockerNetwork contains the name of a docker network.
type DockerNetwork struct {
logger testutil.Logger
Name string
Subnet *net.IPNet
containers []*Docker
}
// NewDockerNetwork sets up the struct for a Docker network. Names of networks
// will be unique.
func NewDockerNetwork(logger testutil.Logger) *DockerNetwork {
return &DockerNetwork{
logger: logger,
Name: testutil.RandomID(logger.Name()),
}
}
// Create calls 'docker network create'.
func (n *DockerNetwork) Create(args ...string) error {
a := []string{"docker", "network", "create"}
if n.Subnet != nil {
a = append(a, fmt.Sprintf("--subnet=%s", n.Subnet))
}
a = append(a, args...)
a = append(a, n.Name)
return testutil.Command(n.logger, a...).Run()
}
// Connect calls 'docker network connect' with the arguments provided.
func (n *DockerNetwork) Connect(container *Docker, args ...string) error {
a := []string{"docker", "network", "connect"}
a = append(a, args...)
a = append(a, n.Name, container.Name)
if err := testutil.Command(n.logger, a...).Run(); err != nil {
return err
}
n.containers = append(n.containers, container)
return nil
}
// Cleanup cleans up the docker network and all the containers attached to it.
func (n *DockerNetwork) Cleanup() error {
for _, c := range n.containers {
// Don't propagate the error, it might be that the container
// was already cleaned up.
if err := c.Kill(); err != nil {
n.logger.Logf("unable to kill container during cleanup: %s", err)
}
}
if err := testutil.Command(n.logger, "docker", "network", "rm", n.Name).Run(); err != nil {
return err
}
return nil
}
// Docker contains the name and the runtime of a docker container.
type Docker struct {
logger testutil.Logger
@@ -313,7 +369,9 @@ func (d *Docker) argsFor(r *RunOpts, command string, p []string) (rv []string) {
rv = append(rv, d.Name)
} else {
rv = append(rv, d.mounts...)
rv = append(rv, fmt.Sprintf("--runtime=%s", d.Runtime))
if len(d.Runtime) > 0 {
rv = append(rv, fmt.Sprintf("--runtime=%s", d.Runtime))
}
rv = append(rv, fmt.Sprintf("--name=%s", d.Name))
rv = append(rv, testutil.ImageByName(r.Image))
}
@@ -481,6 +539,56 @@ func (d *Docker) FindIP() (net.IP, error) {
return ip, nil
}
// A NetworkInterface is container's network interface information.
type NetworkInterface struct {
IPv4 net.IP
MAC net.HardwareAddr
}
// ListNetworks returns the network interfaces of the container, keyed by
// Docker network name.
func (d *Docker) ListNetworks() (map[string]NetworkInterface, error) {
const format = `{{json .NetworkSettings.Networks}}`
out, err := testutil.Command(d.logger, "docker", "inspect", "-f", format, d.Name).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error network interfaces: %q: %w", string(out), err)
}
networks := map[string]map[string]string{}
if err := json.Unmarshal(out, &networks); err != nil {
return nil, fmt.Errorf("error decoding network interfaces: %w", err)
}
interfaces := map[string]NetworkInterface{}
for name, iface := range networks {
var netface NetworkInterface
rawIP := strings.TrimSpace(iface["IPAddress"])
if rawIP != "" {
ip := net.ParseIP(rawIP)
if ip == nil {
return nil, fmt.Errorf("invalid IP: %q", rawIP)
}
// Docker's IPAddress field is IPv4. The IPv6 address
// is stored in the GlobalIPv6Address field.
netface.IPv4 = ip
}
rawMAC := strings.TrimSpace(iface["MacAddress"])
if rawMAC != "" {
mac, err := net.ParseMAC(rawMAC)
if err != nil {
return nil, fmt.Errorf("invalid MAC: %q: %w", rawMAC, err)
}
netface.MAC = mac
}
interfaces[name] = netface
}
return interfaces, nil
}
// SandboxPid returns the PID to the sandbox process.
func (d *Docker) SandboxPid() (int, error) {
out, err := testutil.Command(d.logger, "docker", "inspect", "-f={{.State.Pid}}", d.Name).CombinedOutput()
+21
View File
@@ -18,6 +18,27 @@ Packetimpact aims to provide:
* **Control-flow** like for loops, conditionals, and variables.
* **Flexibilty** to specify every byte in a packet or use multiple sockets.
## How to run packetimpact tests?
Build the test container image by running the following at the root of the
repository:
```bash
$ make load-packetimpact
```
Run a test, e.g. `fin_wait2_timeout`, against Linux:
```bash
$ bazel test //test/packetimpact/tests:fin_wait2_timeout_linux_test
```
Run the same test, but against gVisor:
```bash
$ bazel test //test/packetimpact/tests:fin_wait2_timeout_netstack_test
```
## When to use packetimpact?
There are a few ways to write networking tests for gVisor currently:
+15
View File
@@ -0,0 +1,15 @@
load("//tools:defs.bzl", "go_library")
package(
licenses = ["notice"],
)
go_library(
name = "netdevs",
srcs = ["netdevs.go"],
visibility = ["//test/packetimpact:__subpackages__"],
deps = [
"//pkg/tcpip",
"//pkg/tcpip/header",
],
)
+104
View File
@@ -0,0 +1,104 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package netdevs contains utilities for working with network devices.
package netdevs
import (
"fmt"
"net"
"regexp"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
// A DeviceInfo represents a network device.
type DeviceInfo struct {
MAC net.HardwareAddr
IPv4Addr net.IP
IPv4Net *net.IPNet
IPv6Addr net.IP
IPv6Net *net.IPNet
}
var (
deviceLine = regexp.MustCompile(`^\s*\d+: (\w+)`)
linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`)
)
// ParseDevices parses the output from `ip addr show` into a map from device
// name to information about the device.
func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
var currentDevice string
var currentInfo DeviceInfo
deviceInfos := make(map[string]DeviceInfo)
for _, line := range strings.Split(cmdOutput, "\n") {
if m := deviceLine.FindStringSubmatch(line); m != nil {
if currentDevice != "" {
deviceInfos[currentDevice] = currentInfo
}
currentInfo = DeviceInfo{}
currentDevice = m[1]
} else if m := linkLine.FindStringSubmatch(line); m != nil {
mac, err := net.ParseMAC(m[1])
if err != nil {
return nil, err
}
currentInfo.MAC = mac
} else if m := inetLine.FindStringSubmatch(line); m != nil {
ipv4Addr, ipv4Net, err := net.ParseCIDR(m[1])
if err != nil {
return nil, err
}
currentInfo.IPv4Addr = ipv4Addr
currentInfo.IPv4Net = ipv4Net
} else if m := inet6Line.FindStringSubmatch(line); m != nil {
ipv6Addr, ipv6Net, err := net.ParseCIDR(m[1])
if err != nil {
return nil, err
}
currentInfo.IPv6Addr = ipv6Addr
currentInfo.IPv6Net = ipv6Net
}
}
if currentDevice != "" {
deviceInfos[currentDevice] = currentInfo
}
return deviceInfos, nil
}
// MACToIP converts the MAC address to an IPv6 link local address as described
// in RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
func MACToIP(mac net.HardwareAddr) net.IP {
addr := make([]byte, header.IPv6AddressSize)
addr[0] = 0xfe
addr[1] = 0x80
header.EthernetAdddressToModifiedEUI64IntoBuf(tcpip.LinkAddress(mac), addr[8:])
return net.IP(addr)
}
// FindDeviceByIP finds a DeviceInfo and device name from an IP address in the
// output of ParseDevices.
func FindDeviceByIP(ip net.IP, devices map[string]DeviceInfo) (string, DeviceInfo, error) {
for dev, info := range devices {
if info.IPv4Addr.Equal(ip) {
return dev, info, nil
}
}
return "", DeviceInfo{}, fmt.Errorf("can't find %s on any interface", ip)
}
+20
View File
@@ -0,0 +1,20 @@
load("//tools:defs.bzl", "go_test")
package(
default_visibility = ["//test/packetimpact:__subpackages__"],
licenses = ["notice"],
)
go_test(
name = "packetimpact_test",
srcs = ["packetimpact_test.go"],
tags = [
# Not intended to be run directly.
"local",
"manual",
],
deps = [
"//pkg/test/dockerutil",
"//test/packetimpact/netdevs",
],
)
@@ -11,12 +11,10 @@ def _packetimpact_test_impl(ctx):
# permission problems, because all runfiles may not be owned by the
# current user, and no other users will be mapped in that namespace.
# Make sure that everything is readable here.
"find . -type f -exec chmod a+rx {} \\;",
"find . -type d -exec chmod a+rx {} \\;",
"%s %s --posix_server_binary %s --testbench_binary %s $@\n" % (
"find . -type f -or -type d -exec chmod a+rx {} \\;",
"%s %s --testbench_binary %s $@\n" % (
test_runner.short_path,
" ".join(ctx.attr.flags),
ctx.files._posix_server_binary[0].short_path,
ctx.files.testbench_binary[0].short_path,
),
])
@@ -38,7 +36,7 @@ _packetimpact_test = rule(
"_test_runner": attr.label(
executable = True,
cfg = "target",
default = ":test_runner",
default = ":packetimpact_test",
),
"_posix_server_binary": attr.label(
cfg = "target",
@@ -69,6 +67,7 @@ def packetimpact_linux_test(
Args:
name: name of the test
testbench_binary: the testbench binary
expect_failure: the test must fail
**kwargs: all the other args, forwarded to _packetimpact_test
"""
expect_failure_flag = ["--expect_failure"] if expect_failure else []
@@ -113,8 +112,8 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure
name: name of the test
size: size of the test
pure: make a static go binary
expect_linux_failure: expect the test to fail for Linux
expect_netstack_failure: expect the test to fail for Netstack
expect_linux_failure: the test must fail for Linux
expect_netstack_failure: the test must fail for Netstack
**kwargs: all the other args, forwarded to go_test
"""
testbench_binary = name + "_test"
@@ -0,0 +1,312 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The runner starts docker containers and networking for a packetimpact test.
package packetimpact_test
import (
"flag"
"fmt"
"log"
"math/rand"
"net"
"path"
"strings"
"testing"
"time"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/packetimpact/netdevs"
)
// stringList implements flag.Value.
type stringList []string
// String implements flag.Value.String.
func (l *stringList) String() string {
return strings.Join(*l, ",")
}
// Set implements flag.Value.Set.
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
var (
dutPlatform = flag.String("dut_platform", "", "either \"linux\" or \"netstack\"")
testbenchBinary = flag.String("testbench_binary", "", "path to the testbench binary")
tshark = flag.Bool("tshark", false, "use more verbose tshark in logs instead of tcpdump")
extraTestArgs = stringList{}
expectFailure = flag.Bool("expect_failure", false, "expect that the test will fail when run")
dutAddr = net.IPv4(0, 0, 0, 10)
testbenchAddr = net.IPv4(0, 0, 0, 20)
)
const ctrlPort = "40000"
// logger implements testutil.Logger.
//
// Labels logs based on their source and formats multi-line logs.
type logger string
// Name implements testutil.Logger.Name.
func (l logger) Name() string {
return string(l)
}
// Logf implements testutil.Logger.Logf.
func (l logger) Logf(format string, args ...interface{}) {
lines := strings.Split(fmt.Sprintf(format, args...), "\n")
log.Printf("%s: %s", l, lines[0])
for _, line := range lines[1:] {
log.Printf("%*s %s", len(l), "", line)
}
}
func TestOne(t *testing.T) {
flag.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
flag.Parse()
if *dutPlatform != "linux" && *dutPlatform != "netstack" {
t.Fatal("--dut_platform should be either linux or netstack")
}
if *testbenchBinary == "" {
t.Fatal("--testbench_binary is missing")
}
if *dutPlatform == "netstack" {
if _, err := dockerutil.RuntimePath(); err != nil {
t.Fatal("--runtime is missing or invalid with --dut_platform=netstack:", err)
}
}
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.NewDockerNetwork(logger("ctrlNet"))
testNet := dockerutil.NewDockerNetwork(logger("testNet"))
for _, dn := range []*dockerutil.DockerNetwork{ctrlNet, testNet} {
for {
if err := createDockerNetwork(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
}
break
}
defer func(dn *dockerutil.DockerNetwork) {
if err := dn.Cleanup(); err != nil {
t.Errorf("unable to cleanup container %s: %s", dn.Name, err)
}
}(dn)
}
runOpts := dockerutil.RunOpts{
Image: "packetimpact",
CapAdd: []string{"NET_ADMIN"},
Extra: []string{"--sysctl", "net.ipv6.conf.all.disable_ipv6=0", "--rm"},
Foreground: true,
}
// Create the Docker container for the DUT.
dut := dockerutil.MakeDocker(logger("dut"))
if *dutPlatform == "linux" {
dut.Runtime = ""
}
const containerPosixServerBinary = "/packetimpact/posix_server"
dut.CopyFiles("/packetimpact", "/test/packetimpact/dut/posix_server")
if err := dut.Create(runOpts, containerPosixServerBinary, "--ip=0.0.0.0", "--port="+ctrlPort); err != nil {
t.Fatalf("unable to create container %s: %s", dut.Name, err)
}
defer dut.CleanUp()
// Add ctrlNet as eth1 and testNet as eth2.
const testNetDev = "eth2"
if err := addNetworks(dut, dutAddr, []*dockerutil.DockerNetwork{ctrlNet, testNet}); err != nil {
t.Fatal(err)
}
if err := dut.Start(); err != nil {
t.Fatalf("unable to start container %s: %s", dut.Name, err)
}
if _, err := dut.WaitForOutput("Server listening.*\n", 60*time.Second); err != nil {
t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.Name, err)
}
dutTestDevice, dutDeviceInfo, err := deviceByIP(dut, 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.Exec(dockerutil.RunOpts{}, "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.Name, err)
}
// Now try again, to make sure that it worked.
_, dutDeviceInfo, err = deviceByIP(dut, addressInSubnet(dutAddr, *testNet.Subnet))
if err != nil {
t.Fatal(err)
}
remoteIPv6 = dutDeviceInfo.IPv6Addr
if remoteIPv6 == nil {
t.Fatal("unable to set IPv6 address on container", dut.Name)
}
}
// Create the Docker container for the testbench.
testbench := dockerutil.MakeDocker(logger("testbench"))
testbench.Runtime = "" // The testbench always runs on Linux.
tbb := path.Base(*testbenchBinary)
containerTestbenchBinary := "/packetimpact/" + tbb
testbench.CopyFiles("/packetimpact", "/test/packetimpact/tests/"+tbb)
// 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", testNetDev,
}
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", testNetDev,
"-o", "tcp.check_checksum:TRUE",
"-o", "udp.check_checksum:TRUE",
}
snifferRegex = "Capturing on.*\n"
}
if err := testbench.Create(runOpts, snifferArgs...); err != nil {
t.Fatalf("unable to create container %s: %s", testbench.Name, err)
}
defer testbench.CleanUp()
// Add ctrlNet as eth1 and testNet as eth2.
if err := addNetworks(testbench, testbenchAddr, []*dockerutil.DockerNetwork{ctrlNet, testNet}); err != nil {
t.Fatal(err)
}
if err := testbench.Start(); err != nil {
t.Fatalf("unable to start container %s: %s", testbench.Name, err)
}
// Kill so that it will flush output.
defer testbench.Exec(dockerutil.RunOpts{}, "killall", snifferArgs[0])
if _, err := testbench.WaitForOutput(snifferRegex, 60*time.Second); err != nil {
t.Fatalf("sniffer on %s never listened: %s", dut.Name, err)
}
// Because the Linux kernel receives the SYN-ACK but didn't send the SYN it
// will issue a RST. To prevent this IPtables can be used to filter out all
// incoming packets. The raw socket that packetimpact tests use will still see
// everything.
if _, err := testbench.Exec(dockerutil.RunOpts{}, "iptables", "-A", "INPUT", "-i", testNetDev, "-j", "DROP"); err != nil {
t.Fatalf("unable to Exec iptables on container %s: %s", testbench.Name, err)
}
// FIXME(b/156449515): Some piece of the system has a race. The old
// bash script version had a sleep, so we have one too. The race should
// be fixed and this sleep removed.
time.Sleep(time.Second)
// Start a packetimpact test on the test bench. The packetimpact test sends
// and receives packets and also sends POSIX socket commands to the
// posix_server to be executed on the DUT.
testArgs := []string{containerTestbenchBinary}
testArgs = append(testArgs, extraTestArgs...)
testArgs = append(testArgs,
"--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(),
"--device", testNetDev,
)
_, err = testbench.Exec(dockerutil.RunOpts{}, testArgs...)
if !*expectFailure && err != nil {
t.Fatal("test failed:", err)
}
if *expectFailure && err == nil {
t.Fatal("test failure expected but the test succeeded, enable the test and mark the corresponding bug as fixed")
}
}
func addNetworks(d *dockerutil.Docker, addr net.IP, networks []*dockerutil.DockerNetwork) error {
for _, dn := range networks {
ip := addressInSubnet(addr, *dn.Subnet)
// Connect to the network with the specified IP address.
if err := dn.Connect(d, "--ip", ip.String()); err != nil {
return fmt.Errorf("unable to connect container %s to network %s: %w", d.Name, dn.Name, err)
}
}
return nil
}
// addressInSubnet combines the subnet provided with the address and returns a
// new address. The return address bits come from the subnet where the mask is 1
// and from the ip address where the mask is 0.
func addressInSubnet(addr net.IP, subnet net.IPNet) net.IP {
var octets []byte
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)
}
// makeDockerNetwork makes a randomly-named network that will start with the
// namePrefix. The network will be a random /24 subnet.
func createDockerNetwork(n *dockerutil.DockerNetwork) error {
randSource := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(randSource)
// Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
ip := net.IPv4(byte(r1.Intn(224-192)+192), byte(r1.Intn(256)), byte(r1.Intn(256)), 0)
n.Subnet = &net.IPNet{
IP: ip,
Mask: ip.DefaultMask(),
}
return n.Create()
}
// deviceByIP finds a deviceInfo and device name from an IP address.
func deviceByIP(d *dockerutil.Docker, ip net.IP) (string, netdevs.DeviceInfo, error) {
out, err := d.Exec(dockerutil.RunOpts{}, "ip", "addr", "show")
if err != nil {
return "", netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w", d.Name, err)
}
devs, err := netdevs.ParseDevices(out)
if err != nil {
return "", netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w", d.Name, err)
}
testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs)
if err != nil {
return "", netdevs.DeviceInfo{}, fmt.Errorf("can't find deviceInfo for container %s: %w", d.Name, err)
}
return testDevice, deviceInfo, nil
}
+1
View File
@@ -21,6 +21,7 @@ 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",
+2 -2
View File
@@ -114,12 +114,12 @@ var _ layerState = (*etherState)(nil)
func newEtherState(out, in Ether) (*etherState, error) {
lMAC, err := tcpip.ParseMACAddress(LocalMAC)
if err != nil {
return nil, err
return nil, fmt.Errorf("parsing local MAC: %q: %w", LocalMAC, err)
}
rMAC, err := tcpip.ParseMACAddress(RemoteMAC)
if err != nil {
return nil, err
return nil, fmt.Errorf("parsing remote MAC: %q: %w", RemoteMAC, err)
}
s := etherState{
out: Ether{SrcAddr: &lMAC, DstAddr: &rMAC},
+6
View File
@@ -16,6 +16,7 @@ package testbench
import (
"context"
"flag"
"net"
"strconv"
"syscall"
@@ -37,6 +38,11 @@ type DUT struct {
// NewDUT creates a new connection with the DUT over gRPC.
func NewDUT(t *testing.T) DUT {
flag.Parse()
if err := genPseudoFlags(); err != nil {
t.Fatal("generating psuedo flags:", err)
}
posixServerAddress := POSIXServerIP + ":" + strconv.Itoa(POSIXServerPort)
conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive}))
if err != nil {
@@ -16,7 +16,6 @@ package testbench
import (
"encoding/binary"
"flag"
"fmt"
"math"
"net"
@@ -41,7 +40,6 @@ func htons(x uint16) uint16 {
// NewSniffer creates a Sniffer connected to *device.
func NewSniffer(t *testing.T) (Sniffer, error) {
flag.Parse()
snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
if err != nil {
return Sniffer{}, err
@@ -136,7 +134,6 @@ type Injector struct {
// NewInjector creates a new injector on *device.
func NewInjector(t *testing.T) (Injector, error) {
flag.Parse()
ifInfo, err := net.InterfaceByName(Device)
if err != nil {
return Injector{}, err
+29 -2
View File
@@ -16,7 +16,12 @@ package testbench
import (
"flag"
"fmt"
"net"
"os/exec"
"time"
"gvisor.dev/gvisor/test/packetimpact/netdevs"
)
var (
@@ -55,9 +60,31 @@ func RegisterFlags(fs *flag.FlagSet) {
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(&LocalIPv6, "local_ipv6", LocalIPv6, "local IPv6 address for test packets")
fs.StringVar(&RemoteIPv6, "remote_ipv6", RemoteIPv6, "remote IPv6 address for test packets")
fs.StringVar(&LocalMAC, "local_mac", LocalMAC, "local mac address for test packets")
fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
fs.StringVar(&Device, "device", Device, "local device for test packets")
}
// 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)
}
_, deviceInfo, err := netdevs.FindDeviceByIP(net.ParseIP(LocalIPv4), devs)
if err != nil {
return fmt.Errorf("can't find deviceInfo: %w", err)
}
LocalMAC = deviceInfo.MAC.String()
LocalIPv6 = deviceInfo.IPv6Addr.String()
return nil
}
+1 -6
View File
@@ -1,4 +1,4 @@
load("defs.bzl", "packetimpact_go_test")
load("//test/packetimpact/runner:defs.bzl", "packetimpact_go_test")
package(
default_visibility = ["//test/packetimpact:__subpackages__"],
@@ -177,8 +177,3 @@ packetimpact_go_test(
"@org_golang_x_sys//unix:go_default_library",
],
)
sh_binary(
name = "test_runner",
srcs = ["test_runner.sh"],
)
-325
View File
@@ -1,325 +0,0 @@
#!/bin/bash
# Copyright 2020 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Run a packetimpact test. Two docker containers are made, one for the
# Device-Under-Test (DUT) and one for the test bench. Each is attached with
# two networks, one for control packets that aid the test and one for test
# packets which are sent as part of the test and observed for correctness.
set -euxo pipefail
function failure() {
local lineno=$1
local msg=$2
local filename="$0"
echo "FAIL: $filename:$lineno: $msg"
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR
declare -r LONGOPTS="dut_platform:,posix_server_binary:,testbench_binary:,runtime:,tshark,extra_test_arg:,expect_failure"
# Don't use declare below so that the error from getopt will end the script.
PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@")
eval set -- "$PARSED"
declare -a EXTRA_TEST_ARGS
while true; do
case "$1" in
--dut_platform)
# Either "linux" or "netstack".
declare -r DUT_PLATFORM="$2"
shift 2
;;
--posix_server_binary)
declare -r POSIX_SERVER_BINARY="$2"
shift 2
;;
--testbench_binary)
declare -r TESTBENCH_BINARY="$2"
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
;;
--tshark)
declare -r TSHARK="1"
shift 1
;;
--extra_test_arg)
EXTRA_TEST_ARGS+="$2"
shift 2
;;
--expect_failure)
declare -r EXPECT_FAILURE="1"
shift 1
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
esac
done
# All the other arguments are scripts.
declare -r scripts="$@"
# Check that the required flags are defined in a way that is safe for "set -u".
if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then
if [[ -z "${RUNTIME-}" ]]; then
echo "FAIL: Missing --runtime argument: ${RUNTIME-}"
exit 2
fi
declare -r RUNTIME_ARG="--runtime ${RUNTIME}"
elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then
declare -r RUNTIME_ARG=""
else
echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}"
exit 2
fi
if [[ ! -f "${POSIX_SERVER_BINARY-}" ]]; then
echo "FAIL: Bad or missing --posix_server_binary: ${POSIX_SERVER-}"
exit 2
fi
if [[ ! -f "${TESTBENCH_BINARY-}" ]]; then
echo "FAIL: Bad or missing --testbench_binary: ${TESTBENCH_BINARY-}"
exit 2
fi
function new_net_prefix() {
# Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)"
}
# Variables specific to the control network and interface start with CTRL_.
# Variables specific to the test network and interface start with TEST_.
# Variables specific to the DUT start with DUT_.
# Variables specific to the test bench start with TESTBENCH_.
# Use random numbers so that test networks don't collide.
declare CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
declare CTRL_NET_PREFIX=$(new_net_prefix)
declare TEST_NET="test_net-${RANDOM}${RANDOM}"
declare TEST_NET_PREFIX=$(new_net_prefix)
# On both DUT and test bench, testing packets are on the eth2 interface.
declare -r TEST_DEVICE="eth2"
# Number of bits in the *_NET_PREFIX variables.
declare -r NET_MASK="24"
# Last bits of the DUT's IP address.
declare -r DUT_NET_SUFFIX=".10"
# Control port.
declare -r CTRL_PORT="40000"
# Last bits of the test bench's IP address.
declare -r TESTBENCH_NET_SUFFIX=".20"
declare -r TIMEOUT="60"
declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetimpact"
# Make sure that docker is installed.
docker --version
function finish {
local cleanup_success=1
if [[ -z "${TSHARK-}" ]]; then
# Kill tcpdump so that it will flush output.
docker exec -t "${TESTBENCH}" \
killall tcpdump || \
cleanup_success=0
else
# Kill tshark so that it will flush output.
docker exec -t "${TESTBENCH}" \
killall tshark || \
cleanup_success=0
fi
for net in "${CTRL_NET}" "${TEST_NET}"; do
# Kill all processes attached to ${net}.
for docker_command in "kill" "rm"; do
(docker network inspect "${net}" \
--format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \
| xargs -r docker "${docker_command}") || \
cleanup_success=0
done
# Remove the network.
docker network rm "${net}" || \
cleanup_success=0
done
if ((!$cleanup_success)); then
echo "FAIL: Cleanup command failed"
exit 4
fi
}
trap finish EXIT
# Subnet for control packets between test bench and DUT.
while ! docker network create \
"--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do
sleep 0.1
CTRL_NET_PREFIX=$(new_net_prefix)
CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
done
# Subnet for the packets that are part of the test.
while ! docker network create \
"--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do
sleep 0.1
TEST_NET_PREFIX=$(new_net_prefix)
TEST_NET="test_net-${RANDOM}${RANDOM}"
done
docker pull "${IMAGE_TAG}"
# Create the DUT container and connect to network.
DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \
--cap-add NET_ADMIN \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
--stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
docker network connect "${CTRL_NET}" \
--ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
|| (docker kill ${DUT}; docker rm ${DUT}; false)
docker network connect "${TEST_NET}" \
--ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
|| (docker kill ${DUT}; docker rm ${DUT}; false)
docker start "${DUT}"
# Create the test bench container and connect to network.
TESTBENCH=$(docker create --privileged --rm \
--cap-add NET_ADMIN \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
--stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
docker network connect "${CTRL_NET}" \
--ip "${CTRL_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
|| (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
docker network connect "${TEST_NET}" \
--ip "${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
|| (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
docker start "${TESTBENCH}"
# Start the posix_server in the DUT.
declare -r DOCKER_POSIX_SERVER_BINARY="/$(basename ${POSIX_SERVER_BINARY})"
docker cp -L ${POSIX_SERVER_BINARY} "${DUT}:${DOCKER_POSIX_SERVER_BINARY}"
docker exec -t "${DUT}" \
/bin/bash -c "${DOCKER_POSIX_SERVER_BINARY} \
--ip ${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
--port ${CTRL_PORT}" &
# Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will
# issue a RST. To prevent this IPtables can be used to filter those out.
docker exec "${TESTBENCH}" \
iptables -A INPUT -i ${TEST_DEVICE} -j DROP
# Wait for the DUT server to come up. Attempt to connect to it from the test
# bench every 100 milliseconds until success.
while ! docker exec "${TESTBENCH}" \
nc -zv "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${CTRL_PORT}"; do
sleep 0.1
done
declare -r REMOTE_MAC=$(docker exec -t "${DUT}" ip link show \
"${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
declare -r LOCAL_MAC=$(docker exec -t "${TESTBENCH}" ip link show \
"${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
declare REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show scope link \
"${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
declare -r LOCAL_IPV6=$(docker exec -t "${TESTBENCH}" ip addr show scope link \
"${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
# Netstack as DUT doesn't assign IPv6 addresses automatically so do it if
# needed. Convert the MAC address to an IPv6 link local address as described in
# RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
if [[ -z "${REMOTE_IPV6}" ]]; then
# Split the octets of the MAC into an array of strings.
IFS=":" read -a REMOTE_OCTETS <<< "${REMOTE_MAC}"
# Flip the global bit.
REMOTE_OCTETS[0]=$(printf '%x' "$((0x${REMOTE_OCTETS[0]} ^ 2))")
# Add the IPv6 address.
docker exec "${DUT}" \
ip addr add $(printf 'fe80::%02x%02x:%02xff:fe%02x:%02x%02x/64' \
"0x${REMOTE_OCTETS[0]}" "0x${REMOTE_OCTETS[1]}" "0x${REMOTE_OCTETS[2]}" \
"0x${REMOTE_OCTETS[3]}" "0x${REMOTE_OCTETS[4]}" "0x${REMOTE_OCTETS[5]}") \
scope link \
dev "${TEST_DEVICE}"
# Re-extract the IPv6 address.
# TODO(eyalsoha): Add "scope link" below when netstack supports correctly
# creating link-local IPv6 addresses.
REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show \
"${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
fi
declare -r DOCKER_TESTBENCH_BINARY="/$(basename ${TESTBENCH_BINARY})"
docker cp -L "${TESTBENCH_BINARY}" "${TESTBENCH}:${DOCKER_TESTBENCH_BINARY}"
if [[ -z "${TSHARK-}" ]]; then
# Run tcpdump in the test bench unbuffered, without dns resolution, just on
# the interface with the test packets.
docker exec -t "${TESTBENCH}" \
tcpdump -S -vvv -U -n -i "${TEST_DEVICE}" \
net "${TEST_NET_PREFIX}/24" or \
host "${REMOTE_IPV6}" or \
host "${LOCAL_IPV6}" &
else
# Run tshark in the test bench unbuffered, without dns resolution, just on the
# interface with the test packets.
docker exec -t "${TESTBENCH}" \
tshark -V -l -n -i "${TEST_DEVICE}" \
-o tcp.check_checksum:TRUE \
-o udp.check_checksum:TRUE \
net "${TEST_NET_PREFIX}/24" or \
host "${REMOTE_IPV6}" or \
host "${LOCAL_IPV6}" &
fi
# tcpdump and tshark take time to startup
sleep 3
# Start a packetimpact test on the test bench. The packetimpact test sends and
# receives packets and also sends POSIX socket commands to the posix_server to
# be executed on the DUT.
docker exec \
-e XML_OUTPUT_FILE="/test.xml" \
-e TEST_TARGET \
-t "${TESTBENCH}" \
/bin/bash -c "${DOCKER_TESTBENCH_BINARY} \
${EXTRA_TEST_ARGS[@]-} \
--posix_server_ip=${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
--posix_server_port=${CTRL_PORT} \
--remote_ipv4=${TEST_NET_PREFIX}${DUT_NET_SUFFIX} \
--local_ipv4=${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX} \
--remote_ipv6=${REMOTE_IPV6} \
--local_ipv6=${LOCAL_IPV6} \
--remote_mac=${REMOTE_MAC} \
--local_mac=${LOCAL_MAC} \
--device=${TEST_DEVICE}" && true
declare -r TEST_RESULT="${?}"
if [[ -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" != 0 ]]; then
echo 'FAIL: This test was expected to pass.'
exit ${TEST_RESULT}
fi
if [[ ! -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" == 0 ]]; then
echo 'FAIL: This test was expected to fail but passed. Enable the test and' \
'mark the corresponding bug as fixed.'
exit 1
fi
echo PASS: No errors.