• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
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
155     let tap_name = "test_tap";
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]
204 #[ignore = "b/333090169 test is flaky"]
tap_hotplug_add_remove_add()205 fn tap_hotplug_add_remove_add() {
206     call_test_with_sudo("tap_hotplug_add_remove_add_impl");
207 }
208 
209 /// Implementation for tap_hotplug_add_remove_rapid_add
210 ///
211 /// This test will fail by itself due to permission.
212 #[ignore = "Only to be called by tap_hotplug_add_remove_rapid_add"]
213 #[test]
tap_hotplug_add_remove_rapid_add_impl()214 fn tap_hotplug_add_remove_rapid_add_impl() {
215     let wait_timeout = Duration::from_secs(5);
216     // Setup VM start parameter.
217     let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "1".to_owned()]);
218     let mut vm = TestVm::new(config).unwrap();
219 
220     //Setup test tap
221     let tap_name = "test_tap";
222     setup_tap_device(
223         tap_name.as_bytes(),
224         "100.115.92.5".parse().unwrap(),
225         "255.255.255.252".parse().unwrap(),
226         "a0:b0:c0:d0:e0:f0".parse().unwrap(),
227     );
228 
229     assert!(poll_until_true(
230         &mut vm,
231         |vm| { count_virtio_net_devices(vm) == 0 },
232         wait_timeout
233     ));
234     // Hotplug tap.
235     vm.hotplug_tap(tap_name).unwrap();
236     // Wait until virtio-net device appears in guest OS.
237     assert!(poll_until_true(
238         &mut vm,
239         |vm| { count_virtio_net_devices(vm) == 1 },
240         wait_timeout
241     ));
242 
243     // Remove hotplugged tap device, then hotplug again without waiting for guest.
244     vm.remove_pci_device(1).unwrap();
245     vm.hotplug_tap(tap_name).unwrap();
246 
247     // Wait for a while that the guest likely noticed the removal.
248     thread::sleep(Duration::from_millis(500));
249     // Wait until virtio-net device reappears in guest OS. This assertion would fail if the device
250     // added later is not recognized.
251     assert!(poll_until_true(
252         &mut vm,
253         |vm| { count_virtio_net_devices(vm) == 1 },
254         wait_timeout
255     ));
256 
257     drop(vm);
258     Command::new("ip")
259         .args(["link", "delete", tap_name])
260         .status()
261         .unwrap();
262 }
263 
264 /// Checks tap hotplug works with a device added, removed, then rapidly added again.
265 #[test]
266 #[ignore = "b/333090169 test is flaky"]
tap_hotplug_add_remove_rapid_add()267 fn tap_hotplug_add_remove_rapid_add() {
268     call_test_with_sudo("tap_hotplug_add_remove_rapid_add_impl");
269 }
270