gvisor/test/packetimpact/testbench/testbench.go

181 lines
5.7 KiB
Go

// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package testbench has utilities to send and receive packets, and also command
// the DUT to run POSIX functions. It is the packetimpact test API.
package testbench
import (
"encoding/json"
"flag"
"fmt"
"math/rand"
"net"
"testing"
"time"
)
var (
// Native indicates that the test is being run natively.
Native = false
// RPCKeepalive is the gRPC keepalive.
RPCKeepalive = 10 * time.Second
// dutInfosJSON is the json string that describes information about all the
// duts available to use.
dutInfosJSON string
// dutInfo is the pool among which the testbench can choose a DUT to work
// with.
dutInfo chan *DUTInfo
)
// DUTInfo has both network and uname information about the DUT.
type DUTInfo struct {
Uname *DUTUname
Net *DUTTestNet
}
// DUTUname contains information about the DUT from uname.
type DUTUname struct {
Machine string
KernelName string
KernelRelease string
KernelVersion string
OperatingSystem string
}
// IsLinux returns true if the DUT is running Linux.
func (n *DUTUname) IsLinux() bool {
return Native && n.OperatingSystem == "GNU/Linux"
}
// IsGvisor returns true if the DUT is running gVisor.
func (*DUTUname) IsGvisor() bool {
return !Native
}
// IsFuchsia returns true if the DUT is running Fuchsia.
func (n *DUTUname) IsFuchsia() bool {
return Native && n.OperatingSystem == "Fuchsia"
}
// DUTTestNet describes the test network setup on dut and how the testbench
// should connect with an existing DUT.
type DUTTestNet struct {
// LocalMAC is the local MAC address on the test network.
LocalMAC net.HardwareAddr
// RemoteMAC is the DUT's MAC address on the test network.
RemoteMAC net.HardwareAddr
// LocalIPv4 is the local IPv4 address on the test network.
LocalIPv4 net.IP
// RemoteIPv4 is the DUT's IPv4 address on the test network.
RemoteIPv4 net.IP
// IPv4PrefixLength is the network prefix length of the IPv4 test network.
IPv4PrefixLength int
// LocalIPv6 is the local IPv6 address on the test network.
LocalIPv6 net.IP
// RemoteIPv6 is the DUT's IPv6 address on the test network.
RemoteIPv6 net.IP
// LocalDevID is the ID of the local interface on the test network.
LocalDevID uint32
// RemoteDevID is the ID of the remote interface on the test network.
RemoteDevID uint32
// LocalDevName is the device that testbench uses to inject traffic.
LocalDevName string
// RemoteDevName is the device name on the DUT, individual tests can
// use the name to construct tests.
RemoteDevName string
// The following two fields on actually on the control network instead
// of the test network, including them for convenience.
// POSIXServerIP is the POSIX server's IP address on the control network.
POSIXServerIP net.IP
// POSIXServerPort is the UDP port the POSIX server is bound to on the
// control network.
POSIXServerPort uint16
}
// SubnetBroadcast returns the test network's subnet broadcast address.
func (n *DUTTestNet) SubnetBroadcast() net.IP {
addr := append([]byte(nil), n.RemoteIPv4...)
mask := net.CIDRMask(n.IPv4PrefixLength, net.IPv4len*8)
for i := range addr {
addr[i] |= ^mask[i]
}
return addr
}
// registerFlags defines flags and associates them with the package-level
// exported variables above. It should be called by tests in their init
// functions.
func registerFlags(fs *flag.FlagSet) {
fs.BoolVar(&Native, "native", Native, "whether the test is running natively")
fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive")
fs.StringVar(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs")
}
// Initialize initializes the testbench, it parse the flags and sets up the
// pool of test networks for testbench's later use.
func Initialize(fs *flag.FlagSet) {
testing.Init()
registerFlags(fs)
flag.Parse()
if err := loadDUTInfos(); err != nil {
panic(err)
}
}
// loadDUTInfos loads available DUT test infos from the json file, it
// must be called after flag.Parse().
func loadDUTInfos() error {
var dutInfos []DUTInfo
if err := json.Unmarshal([]byte(dutInfosJSON), &dutInfos); err != nil {
return fmt.Errorf("failed to unmarshal JSON: %w", err)
}
if got, want := len(dutInfos), 1; got < want {
return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want)
}
// Using a buffered channel as semaphore
dutInfo = make(chan *DUTInfo, len(dutInfos))
for i := range dutInfos {
dutInfos[i].Net.LocalIPv4 = dutInfos[i].Net.LocalIPv4.To4()
dutInfos[i].Net.RemoteIPv4 = dutInfos[i].Net.RemoteIPv4.To4()
dutInfo <- &dutInfos[i]
}
return nil
}
// GenerateRandomPayload generates a random byte slice of the specified length,
// causing a fatal test failure if it is unable to do so.
func GenerateRandomPayload(t *testing.T, n int) []byte {
t.Helper()
buf := make([]byte, n)
if _, err := rand.Read(buf); err != nil {
t.Fatalf("rand.Read(buf) failed: %s", err)
}
return buf
}
// getDUTInfo returns information about an available DUT from the pool. If no
// DUT is readily available, getDUTInfo blocks until one becomes available.
func getDUTInfo() *DUTInfo {
return <-dutInfo
}
// release returns the DUTInfo back to the pool.
func (info *DUTInfo) release() {
dutInfo <- info
}