|
| 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