1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Integration test for hotplug of tap devices as virtio-net.
6
7 #![cfg(all(unix, target_arch = "x86_64"))]
8
9 use std::net::Ipv4Addr;
10 use std::process::Command;
11 use std::thread;
12 use std::time::Duration;
13 use std::time::Instant;
14
15 use base::sys::linux::ioctl_with_val;
16 use base::test_utils::call_test_with_sudo;
17 use fixture::vm::Config;
18 use fixture::vm::TestVm;
19 use net_util::sys::linux::Tap;
20 use net_util::sys::linux::TapTLinux;
21 use net_util::MacAddress;
22 use net_util::TapTCommon;
23
24 /// Count the number of virtio-net devices.
count_virtio_net_devices(vm: &mut TestVm) -> usize25 fn count_virtio_net_devices(vm: &mut TestVm) -> usize {
26 let lspci_result = vm.exec_in_guest("lspci -n").unwrap();
27 // Count occurance for virtio net device: 1af4:1041
28 lspci_result.stdout.matches("1af4:1041").count()
29 }
30
31 /// Poll func until it returns true, or timeout is exceeded.
poll_until_true<F>(vm: &mut TestVm, func: F, timeout: Duration) -> bool where F: Fn(&mut TestVm) -> bool,32 fn poll_until_true<F>(vm: &mut TestVm, func: F, timeout: Duration) -> bool
33 where
34 F: Fn(&mut TestVm) -> bool,
35 {
36 let poll_interval = Duration::from_millis(100);
37 let start_time = Instant::now();
38 while !func(vm) {
39 if start_time.elapsed() > timeout {
40 return false;
41 }
42 thread::sleep(poll_interval);
43 }
44 true
45 }
46
47 /// setup a tap device for test
setup_tap_device(tap_name: &[u8], ip_addr: Ipv4Addr, netmask: Ipv4Addr, mac_addr: MacAddress)48 fn setup_tap_device(tap_name: &[u8], ip_addr: Ipv4Addr, netmask: Ipv4Addr, mac_addr: MacAddress) {
49 let tap = Tap::new_with_name(tap_name, true, false).unwrap();
50 // SAFETY:
51 // ioctl is safe since we call it with a valid tap fd and check the return value.
52 let ret = unsafe { ioctl_with_val(&tap, net_sys::TUNSETPERSIST, 1) };
53 if ret < 0 {
54 panic!("Failed to persist tap interface");
55 }
56 tap.set_ip_addr(ip_addr).unwrap();
57 tap.set_netmask(netmask).unwrap();
58 tap.set_mac_address(mac_addr).unwrap();
59 tap.set_vnet_hdr_size(16).unwrap();
60 tap.set_offload(0).unwrap();
61 tap.enable().unwrap();
62 // Release tap to be used by the VM.
63 drop(tap);
64 }
65
66 /// Implementation for tap_hotplug_two
67 ///
68 /// This test will fail by itself due to permission.
69 #[ignore = "Only to be called by tap_hotplug_two"]
70 #[test]
tap_hotplug_two_impl()71 fn tap_hotplug_two_impl() {
72 let wait_timeout = Duration::from_secs(5);
73 // Setup VM start parameter.
74 let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "2".to_owned()]);
75 let mut vm = TestVm::new(config).unwrap();
76
77 //Setup test taps. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
78 let tap1_name = "test_tap1";
79 setup_tap_device(
80 tap1_name.as_bytes(),
81 "100.115.92.15".parse().unwrap(),
82 "255.255.255.252".parse().unwrap(),
83 "a0:b0:c0:d0:e0:f1".parse().unwrap(),
84 );
85 let tap2_name = "test_tap2";
86 setup_tap_device(
87 tap2_name.as_bytes(),
88 "100.115.92.25".parse().unwrap(),
89 "255.255.255.252".parse().unwrap(),
90 "a0:b0:c0:d0:e0:f2".parse().unwrap(),
91 );
92
93 // Check number of virtio-net devices after each hotplug.
94 assert!(poll_until_true(
95 &mut vm,
96 |vm| { count_virtio_net_devices(vm) == 0 },
97 wait_timeout
98 ));
99 vm.hotplug_tap(tap1_name).unwrap();
100 assert!(poll_until_true(
101 &mut vm,
102 |vm| { count_virtio_net_devices(vm) == 1 },
103 wait_timeout
104 ));
105 vm.hotplug_tap(tap2_name).unwrap();
106 assert!(poll_until_true(
107 &mut vm,
108 |vm| { count_virtio_net_devices(vm) == 2 },
109 wait_timeout
110 ));
111
112 // Check number of devices after each removal.
113 vm.remove_pci_device(1).unwrap();
114 assert!(poll_until_true(
115 &mut vm,
116 |vm| { count_virtio_net_devices(vm) == 1 },
117 wait_timeout
118 ));
119 vm.remove_pci_device(2).unwrap();
120 assert!(poll_until_true(
121 &mut vm,
122 |vm| { count_virtio_net_devices(vm) == 0 },
123 wait_timeout
124 ));
125
126 drop(vm);
127 Command::new("ip")
128 .args(["link", "delete", tap1_name])
129 .status()
130 .unwrap();
131 Command::new("ip")
132 .args(["link", "delete", tap2_name])
133 .status()
134 .unwrap();
135 }
136
137 /// Checks hotplug works with two tap devices.
138 #[test]
tap_hotplug_two()139 fn tap_hotplug_two() {
140 call_test_with_sudo("tap_hotplug_two_impl");
141 }
142
143 /// Implementation for tap_hotplug_add_remove_add
144 ///
145 /// This test will fail by itself due to permission.
146 #[ignore = "Only to be called by tap_hotplug_add_remove_add"]
147 #[test]
tap_hotplug_add_remove_add_impl()148 fn tap_hotplug_add_remove_add_impl() {
149 let wait_timeout = Duration::from_secs(5);
150 // Setup VM start parameter.
151 let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "1".to_owned()]);
152 let mut vm = TestVm::new(config).unwrap();
153
154 //Setup test tap. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
155 let tap_name = "test_tap3";
156 setup_tap_device(
157 tap_name.as_bytes(),
158 "100.115.92.5".parse().unwrap(),
159 "255.255.255.252".parse().unwrap(),
160 "a0:b0:c0:d0:e0:f0".parse().unwrap(),
161 );
162
163 assert!(poll_until_true(
164 &mut vm,
165 |vm| { count_virtio_net_devices(vm) == 0 },
166 wait_timeout
167 ));
168 // Hotplug tap.
169 vm.hotplug_tap(tap_name).unwrap();
170 // Wait until virtio-net device appears in guest OS.
171 assert!(poll_until_true(
172 &mut vm,
173 |vm| { count_virtio_net_devices(vm) == 1 },
174 wait_timeout
175 ));
176
177 // Remove hotplugged tap device.
178 vm.remove_pci_device(1).unwrap();
179 // Wait until virtio-net device disappears from guest OS.
180 assert!(poll_until_true(
181 &mut vm,
182 |vm| { count_virtio_net_devices(vm) == 0 },
183 wait_timeout
184 ));
185
186 // Hotplug tap again.
187 vm.hotplug_tap(tap_name).unwrap();
188 // Wait until virtio-net device appears in guest OS.
189 assert!(poll_until_true(
190 &mut vm,
191 |vm| { count_virtio_net_devices(vm) == 1 },
192 wait_timeout
193 ));
194
195 drop(vm);
196 Command::new("ip")
197 .args(["link", "delete", tap_name])
198 .status()
199 .unwrap();
200 }
201
202 /// Checks tap hotplug works with a device added, removed, then added again.
203 #[test]
tap_hotplug_add_remove_add()204 fn tap_hotplug_add_remove_add() {
205 call_test_with_sudo("tap_hotplug_add_remove_add_impl");
206 }
207
208 /// Implementation for tap_hotplug_add_remove_rapid_add
209 ///
210 /// This test will fail by itself due to permission.
211 #[ignore = "Only to be called by tap_hotplug_add_remove_rapid_add"]
212 #[test]
tap_hotplug_add_remove_rapid_add_impl()213 fn tap_hotplug_add_remove_rapid_add_impl() {
214 let wait_timeout = Duration::from_secs(5);
215 // Setup VM start parameter.
216 let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "1".to_owned()]);
217 let mut vm = TestVm::new(config).unwrap();
218
219 //Setup test tap. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
220 let tap_name_a = "test_tap4";
221 setup_tap_device(
222 tap_name_a.as_bytes(),
223 "100.115.92.9".parse().unwrap(),
224 "255.255.255.252".parse().unwrap(),
225 "a0:b0:c0:d0:e0:f0".parse().unwrap(),
226 );
227
228 let tap_name_b = "test_tap5";
229 setup_tap_device(
230 tap_name_b.as_bytes(),
231 "100.115.92.1".parse().unwrap(),
232 "255.255.255.252".parse().unwrap(),
233 "a0:b0:c0:d0:e0:f0".parse().unwrap(),
234 );
235
236 assert!(poll_until_true(
237 &mut vm,
238 |vm| { count_virtio_net_devices(vm) == 0 },
239 wait_timeout
240 ));
241 // Hotplug tap.
242 vm.hotplug_tap(tap_name_a).unwrap();
243 // Wait until virtio-net device appears in guest OS.
244 assert!(poll_until_true(
245 &mut vm,
246 |vm| { count_virtio_net_devices(vm) == 1 },
247 wait_timeout
248 ));
249
250 // Remove hotplugged tap device, then hotplug again without waiting for guest.
251 vm.remove_pci_device(1).unwrap();
252 vm.hotplug_tap(tap_name_b).unwrap();
253
254 // Wait for a while that the guest likely noticed the removal.
255 thread::sleep(Duration::from_millis(500));
256 // Wait until virtio-net device reappears in guest OS. This assertion would fail if the device
257 // added later is not recognized.
258 assert!(poll_until_true(
259 &mut vm,
260 |vm| { count_virtio_net_devices(vm) == 1 },
261 wait_timeout
262 ));
263
264 drop(vm);
265 Command::new("ip")
266 .args(["link", "delete", tap_name_a])
267 .status()
268 .unwrap();
269 Command::new("ip")
270 .args(["link", "delete", tap_name_b])
271 .status()
272 .unwrap();
273 }
274
275 /// Checks tap hotplug works with a device added, removed, then rapidly added again.
276 #[test]
tap_hotplug_add_remove_rapid_add()277 fn tap_hotplug_add_remove_rapid_add() {
278 call_test_with_sudo("tap_hotplug_add_remove_rapid_add_impl");
279 }
280