xref: /aosp_15_r20/external/crosvm/e2e_tests/tests/pci_hotplug.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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