summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact/testbench/testbench.go
blob: 38ae9c1d7c4c48298f6d42c47738089d07062096 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 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
}