Skip to content

Commit 5d80d01

Browse files
committed
Add TestIP that uses cpu on 4 Linux architectures
The test runs on Linux on amd64, arm, arm64, and riscv64. Once this is merged, this test can replace the one in u-root. Our goal is to elminate as many u-root VM shell script tests as possible, because we have found undetected errors in them. Tests should be written in Go. We keep this test here to also test cpu. Getting it all working was no small matter. We want it to keep working :-) We extend the client package in small ways to make writing VM tests convenient. The kernel images and initramfs for cpu are built into the package. The tests can be run hermetically by running go test. ghcr.io is only used for go generate, and that will not be frequent (possibly changing only every year or so). The kernels are cached from the u-root ghcr.io docker images. The kernels have a few tweaks, for things missing from the kernel (GRE, VTI) in ghcr.io. The PR for these additions has been submitted for the vmtest repo. The kernels also provide /proc/config.gz. For now, my fixes are local to config_linux.txt files. Note: I have no idea how the vmscript in u-root is passing at this point, with support for GRE, VTI, and other things missing. The script may not be working; there may be errors that are not caught. I've seen ignored errors in other scripts. The compiled-in initramfs package only contains cpud. The vm package builds a u-root image for testing as needed and concatenates it to the cpud initramfs. The VMs start with two network interfaces. The first is used to support cpud; the second interface can be manipulated by the tests. This removes the need to parse VM console output, which can be fragile. IP is configured by ip=dhcp. The test assumes there is a working qemu on the CI. This allows us to run these tests on any kernel/architecture combination that has qemu. Signed-off-by: Ronald G Minnich <[email protected]>
1 parent 6ca66b2 commit 5d80d01

14 files changed

+348
-29
lines changed

vm/build.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ var builds = []build{
3838
env: []string{"GOARCH=amd64", "GOAMD64=v1"},
3939
cmd: []string{},
4040
},
41+
{
42+
os: "linux",
43+
arch: "arm",
44+
kernel: "zImage",
45+
container: "ghcr.io/hugelgupf/vmtest/kernel-arm:main",
46+
env: []string{"GOARCH=arm", "GOARM=5"},
47+
cmd: []string{},
48+
},
4149
{
4250
os: "linux",
4351
arch: "arm64",
@@ -47,14 +55,6 @@ var builds = []build{
4755
env: []string{"GOARCH=arm64"},
4856
cmd: []string{},
4957
},
50-
{
51-
os: "linux",
52-
arch: "arm",
53-
kernel: "zImage",
54-
container: "ghcr.io/hugelgupf/vmtest/kernel-arm:main",
55-
env: []string{"GOARCH=arm", "GOARM=5"},
56-
cmd: []string{},
57-
},
5858
{
5959
os: "linux",
6060
arch: "riscv64",
@@ -130,12 +130,16 @@ func main() {
130130
if len(b.container) == 0 {
131131
continue
132132
}
133-
ref, err := name.ParseReference(b.container)
134-
if err != nil {
135-
log.Fatal(err)
136-
}
133+
if false {
134+
ref, err := name.ParseReference(b.container, name.Insecure)
135+
if err != nil {
136+
log.Fatal(err)
137+
}
137138

138-
img, err := crane.Pull(ref.Name())
139+
fmt.Printf("parse %s to %s", b.container, ref.Name())
140+
}
141+
//img, err := crane.Pull(ref.Name())
142+
img, err := crane.Pull(b.container)
139143
if err != nil {
140144
log.Fatal(err)
141145
}

vm/initramfs_linux_amd64.cpio

4 KB
Binary file not shown.

vm/initramfs_linux_amd64.cpio.gz

413 Bytes
Binary file not shown.

vm/initramfs_linux_arm.cpio

0 Bytes
Binary file not shown.

vm/initramfs_linux_arm.cpio.gz

297 Bytes
Binary file not shown.

vm/initramfs_linux_arm64.cpio

0 Bytes
Binary file not shown.

vm/initramfs_linux_arm64.cpio.gz

370 Bytes
Binary file not shown.

vm/initramfs_linux_riscv64.cpio

0 Bytes
Binary file not shown.

vm/initramfs_linux_riscv64.cpio.gz

-15 Bytes
Binary file not shown.

vm/ip_linux_test.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Copyright 2025 the u-root Authors. All rights reserved
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !race && amd64
6+
7+
package vm_test
8+
9+
import (
10+
"context"
11+
"os"
12+
"strings"
13+
"testing"
14+
"time"
15+
16+
"github.com/u-root/cpu/client"
17+
"github.com/u-root/cpu/vm"
18+
)
19+
20+
func count(s string, t []string) int {
21+
var i int
22+
for _, n := range t {
23+
if strings.Contains(s, n) {
24+
i++
25+
}
26+
}
27+
return i
28+
}
29+
30+
func all(s string, t []string) bool {
31+
return count(s, t) == len(t)
32+
}
33+
34+
func some(s string, t []string) bool {
35+
return count(s, t) > 0
36+
}
37+
38+
func none(s string, t []string) bool {
39+
return count(s, t) == 0
40+
}
41+
42+
// TestIP tests creation and removal of addresses, tunnels, and
43+
// ARP entries with the u-root ip command.
44+
func TestIP(t *testing.T) {
45+
d := t.TempDir()
46+
for _, arch := range []string{"amd64", "arm", "arm64", "riscv64"} {
47+
i, err := vm.New("linux", arch)
48+
if err != nil {
49+
t.Fatalf("Testing kernel=linux arch=%s: got %v, want nil", arch, err)
50+
}
51+
52+
ctx, cancel := context.WithCancel(context.Background())
53+
defer cancel()
54+
55+
t.Logf("image:%s", i)
56+
n, err := i.Uroot(d)
57+
if err != nil {
58+
t.Skipf("skipping this test as we have no uroot command")
59+
}
60+
61+
c, err := i.CommandContext(ctx, d, n)
62+
if err != nil {
63+
t.Fatalf("starting VM: got %v, want nil", err)
64+
}
65+
t.Logf("Start %v", c.Args)
66+
67+
// For debug. It's been needed, but we do not want this spew in
68+
// CI logs, so leave it off.
69+
if false {
70+
c.Stdout, c.Stderr = os.Stdout, os.Stderr
71+
}
72+
if err := i.StartVM(c); err != nil {
73+
t.Fatalf("starting VM: got %v, want nil", err)
74+
}
75+
76+
type iptest struct {
77+
cmd any
78+
delay time.Duration
79+
failok bool
80+
includes []string
81+
excludes []string
82+
}
83+
// This is a slice of iptest slices.
84+
// The intent is that if the first one fails, and it has failok set to false, the rest of the tests
85+
// in that slice are skipped.
86+
for _, iptest := range [][]iptest{
87+
{
88+
{cmd: "ip link set eth1 down", delay: 3 * time.Second},
89+
{cmd: "cat /sys/class/net/eth1/operstate", includes: []string{"down"}},
90+
{cmd: "ip link set eth1 up", delay: 3 * time.Second},
91+
{cmd: "cat /sys/class/net/eth1/operstate", includes: []string{"up"}},
92+
{cmd: []string{"ip", "addr", "add", "192.168.241.1/24", "dev", "eth1"}},
93+
{cmd: []string{"cat", "/proc/net/fib_trie"}, includes: []string{"192.168.241.1"}},
94+
{cmd: "ip route add 192.168.242.0/24 via 192.168.241.1 dev eth1"},
95+
{cmd: "cat /proc/net/route", includes: []string{"00F2A8C0", "01F1A8C0"}},
96+
{cmd: "ip route del 192.168.242.0/24"},
97+
{cmd: "cat /proc/net/route", excludes: []string{"00F2A8C0", "01F1A8C0"}},
98+
{cmd: "ip tunnel add my_test_tunnel mode sit remote 192.168.242.1 local 192.168.241.1 ttl 64"},
99+
{cmd: "cat /proc/net/dev", includes: []string{"my_test_tunnel"}},
100+
{cmd: "ip tunnel del my_test_tunnel"},
101+
{cmd: "cat /proc/net/dev", excludes: []string{"my_test_tunnel"}},
102+
{cmd: "ip tunnel add my_test_tunnel mode sit remote 192.168.242.1 local 192.168.241.1 ttl 64"},
103+
{cmd: "ip tunnel show my_test_tunnel", includes: []string{"my_test_tunnel", "remote 192.168.242.1", "local 192.168.241.1", "ttl 64"}},
104+
{cmd: "ip tunnel del my_test_tunnel"},
105+
{cmd: "cat /proc/net/dev", excludes: []string{"my_test_tunnel"}},
106+
},
107+
{
108+
// Various tunnel tests.
109+
// Add a GRE tunnel with key and tos options
110+
{cmd: "ip tunnel add gre_tunnel mode gre remote 192.168.242.2 local 192.168.241.1 ttl 128 key 1234 tos 10", failok: false, delay: time.Second},
111+
{cmd: "cat /proc/net/dev", includes: []string{"gre_tunnel"}},
112+
// Verify GRE tunnel parameters
113+
{cmd: "ip tunnel show gre_tunnel", includes: []string{"gre_tunnel:", "remote 192.168.242.2", "local 192.168.241.1", "ttl 128", "key 1234", "tos 0xa"}},
114+
{cmd: "ip link set gre_tunnel up"},
115+
{cmd: "ip addr add 10.0.0.1/24 dev gre_tunnel"},
116+
{cmd: []string{"cat", "/proc/net/fib_trie"}, includes: []string{"10.0.0.1"}},
117+
{cmd: "ip link set gre_tunnel down"},
118+
{cmd: "ip tunnel del gre_tunnel"},
119+
{cmd: "cat /proc/net/dev", excludes: []string{"gre_tunnel"}},
120+
},
121+
{
122+
{cmd: "ip tunnel add vti_tunnel mode vti remote 192.168.242.3 local 192.168.241.1 key 5678", failok: false},
123+
124+
// Verify VTI tunnel exists in /proc/net/dev
125+
{cmd: "cat /proc/net/dev", includes: []string{"vti_tunnel"}},
126+
127+
//Verify VTI tunnel parameters
128+
{cmd: "ip tunnel show vti_tunnel", includes: []string{"vti_tunnel:", "remote 192.168.242.3", "local 192.168.241.1", "key 5678"}},
129+
{cmd: "ip link set vti_tunnel up"},
130+
{cmd: "ip addr add 172.16.0.1/30 dev vti_tunnel"},
131+
{cmd: []string{"cat", "/proc/net/fib_trie"}, includes: []string{"172.16.0.1"}},
132+
{cmd: "ip link set vti_tunnel down"},
133+
{cmd: "ip tunnel del vti_tunnel"},
134+
{cmd: "cat /proc/net/dev", excludes: []string{"vti_tunnel"}},
135+
},
136+
{
137+
{cmd: "ip tunnel add ipip_tunnel mode ipip remote 192.168.243.1 local 192.168.241.1 ttl 64", failok: false},
138+
139+
// Verify IPIP tunnel exists in /proc/net/dev
140+
{cmd: "cat /proc/net/dev", includes: []string{"ipip_tunnel"}},
141+
142+
//Verify IPIP tunnel parameters
143+
{cmd: "ip tunnel show ipip_tunnel", includes: []string{"ipip_tunnel:", "remote 192.168.243.1", "local 192.168.241.1", "ttl 64"}},
144+
{cmd: "ip link set ipip_tunnel up"},
145+
{cmd: "ip addr add 172.17.0.1/30 dev ipip_tunnel"},
146+
{cmd: []string{"cat", "/proc/net/fib_trie"}, includes: []string{"172.17.0.1"}},
147+
{cmd: "ip link set ipip_tunnel down"},
148+
{cmd: "ip tunnel del ipip_tunnel"},
149+
{cmd: "cat /proc/net/dev", excludes: []string{"ipip_tunnel"}},
150+
},
151+
{
152+
// ARP tests
153+
{cmd: "ip neigh add 192.168.241.2 lladdr 00:11:22:33:44:55 dev eth1"},
154+
{cmd: "cat /proc/net/arp", includes: []string{"192.168.241.2"}},
155+
156+
// Verify the neighbor entry
157+
{cmd: "ip neigh show dev eth1", includes: []string{"192.168.241.2", "192.168.241.2 dev eth1 lladdr 00:11:22:33:44:55 PERMANENT"}},
158+
//{cmd: "test "$neigh_entry" = "192.168.241.2 dev eth1 lladdr 00:11:22:33:44:55 PERMANENT"", includes: []string{},},
159+
160+
// Replace the entry with another hwaddress, nud state and router flag
161+
{cmd: "ip neigh replace 192.168.241.2 lladdr 11:22:33:44:55:66 dev eth1 nud stale router", includes: []string{}},
162+
163+
// Verify the modified flags
164+
{cmd: "ip neigh show dev eth1", includes: []string{"192.168.241.2", "192.168.241.2 dev eth1 lladdr 11:22:33:44:55:66 router STALE"}},
165+
166+
// Delete the neighbor
167+
{cmd: "ip neigh del 192.168.241.2 dev eth1", includes: []string{}},
168+
{cmd: "cat /proc/net/arp", excludes: []string{"192.168.241.2"}},
169+
170+
// Test IP Neighbor flush capability
171+
// Add 3 neighbors
172+
{cmd: "ip neigh add 192.168.241.5 lladdr aa:bb:cc:dd:ee:ff nud stale dev eth1"},
173+
{cmd: "ip neigh add 192.168.241.6 lladdr aa:bb:cc:11:22:33 nud stale dev eth1"},
174+
{cmd: "ip neigh add 192.168.241.7 lladdr aa:bb:cc:44:55:66 dev eth1"},
175+
176+
// Verify all entries exist
177+
{cmd: "cat /proc/net/arp", includes: []string{"192.168.241.5", "192.168.241.6", "192.168.241.7"}},
178+
179+
// Flush the 2 stale neighbors from the table for eth1
180+
{cmd: "ip neigh flush dev eth1", includes: []string{}},
181+
182+
// Verify the 2 stale entries are gone, the permanent one remains
183+
{cmd: "cat /proc/net/arp", includes: []string{"192.168.241.7"}, excludes: []string{"192.168.241.5", "192.168.241.6"}},
184+
185+
// Delete the IP address from eth1
186+
{cmd: "ip addr del 192.168.241.1/24 dev eth1"},
187+
{cmd: []string{"cat", "/proc/net/fib_trie"}, excludes: []string{"192.168.241.1"}},
188+
// Bring the eth1 interface down
189+
{cmd: "ip link set eth1 down", delay: 2 * time.Second},
190+
{cmd: "cat /sys/class/net/eth1/operstate", includes: []string{"down"}},
191+
},
192+
} {
193+
194+
for _, tt := range iptest {
195+
var cmd []string
196+
switch c := tt.cmd.(type) {
197+
case []string:
198+
cmd = c
199+
case string:
200+
cmd = strings.Fields(c)
201+
default:
202+
t.Fatalf("tt.cmd.Type(): type %T, want string or []string", tt.cmd)
203+
}
204+
t.Logf("Run %s", cmd)
205+
cpu, err := i.CPUCommand(cmd[0], cmd[1:]...)
206+
if err != nil {
207+
t.Errorf("CPUCommand: got %v, want nil", err)
208+
continue
209+
}
210+
if false {
211+
client.SetVerbose(t.Logf)
212+
}
213+
214+
b, err := cpu.CombinedOutput()
215+
216+
if false { // Only enable this if you want to see console output/errors.
217+
t.Logf("%s %v", string(b), err)
218+
}
219+
220+
if err != nil {
221+
if tt.failok {
222+
t.Logf("%s: got %v, want nil, skipping rest of tests in slice", cmd, err)
223+
break
224+
}
225+
t.Fatalf("%s: got %v, want nil", cmd, err)
226+
}
227+
//t.Logf("%q, includes %s?", string(b), tt.includes)
228+
if !all(string(b), tt.includes) {
229+
t.Fatalf("%s: got %s, does not contain all of %s", cmd, string(b), tt.includes)
230+
}
231+
//t.Logf("%q, excludes %s?", string(b), tt.includes)
232+
if some(string(b), tt.excludes) {
233+
t.Fatalf("%s:got %s, contains some of %s and should not", cmd, string(b), tt.excludes)
234+
}
235+
if tt.delay > 0 {
236+
time.Sleep(tt.delay)
237+
}
238+
}
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)