Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/e2e_tests/tests/net.rs
5394 views
1
// Copyright 2025 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
//! Testing virtio-net.
6
#![cfg(any(target_os = "android", target_os = "linux"))]
7
8
use std::fs::File;
9
use std::path::Path;
10
use std::process::Command;
11
use std::process::Stdio;
12
use std::time::Duration;
13
14
use anyhow::anyhow;
15
use fixture::utils::retry_with_delay;
16
use fixture::vhost_user::CmdType;
17
use fixture::vhost_user::Config as VuConfig;
18
use fixture::vhost_user::VhostUserBackend;
19
use fixture::vm::Config as VmConfig;
20
use fixture::vm::TestVm;
21
use tempfile::NamedTempFile;
22
const VIRTIO_NET_F_MRG_RXBUF: usize = 15;
23
const NCAT_RETRIES: usize = 15;
24
const NCAT_RETRY_DELAY: Duration = Duration::from_millis(300);
25
26
pub fn create_vu_net_config(socket: &Path, host_net_name: String, mrg_rxbuf: bool) -> VuConfig {
27
let socket_path = socket.to_str().unwrap();
28
let mut args = vec![
29
"net".to_string(),
30
"--tap-name".to_string(),
31
format!("{socket_path},{host_net_name}").to_string(),
32
];
33
if mrg_rxbuf {
34
args.push("--mrg-rxbuf".to_string());
35
}
36
VuConfig::new(CmdType::Device, "net").extra_args(args)
37
}
38
39
fn create_guest_with_virtio_net_backend(
40
config: VmConfig,
41
host_ip_with_mask: String,
42
host_net_name: String,
43
mrg_rxbuf: bool,
44
vhost_user_mode: bool,
45
) -> anyhow::Result<(Option<VhostUserBackend>, TestVm)> {
46
// Del crosvm_tap if exist
47
Command::new("sudo")
48
.args(["ip", "tuntap", "del", "mode", "tap", &host_net_name])
49
.output()
50
.unwrap_or_else(|_| panic!("Fail to del {host_net_name}"));
51
// Enable crosvm_tap in backend and up
52
Command::new("sudo")
53
.args([
54
"ip",
55
"tuntap",
56
"add",
57
"mode",
58
"tap",
59
"user",
60
"crosvm",
61
&host_net_name,
62
])
63
.output()
64
.unwrap_or_else(|_| panic!("Fail to create {host_net_name}"));
65
Command::new("sudo")
66
.args([
67
"ip",
68
"addr",
69
"add",
70
&host_ip_with_mask,
71
"dev",
72
&host_net_name,
73
])
74
.output()
75
.unwrap_or_else(|_| panic!("Fail to set {host_net_name} address"));
76
Command::new("sudo")
77
.args(["ip", "link", "set", &host_net_name, "up"])
78
.output()
79
.unwrap_or_else(|_| panic!("Fail to up {host_net_name}"));
80
81
let (vu, cfg) = if vhost_user_mode {
82
// Start a vhost-user-net backend firstly
83
let socket = NamedTempFile::new().unwrap();
84
let vu_config = create_vu_net_config(socket.path(), host_net_name, mrg_rxbuf);
85
let vu_device = VhostUserBackend::new_sudo(vu_config).unwrap();
86
(
87
Some(vu_device),
88
config
89
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
90
.with_vhost_user("net", socket.path()),
91
)
92
} else {
93
let mut extra_args = vec!["--mem".to_owned(), "512".to_owned(), "--net".to_owned()];
94
if mrg_rxbuf {
95
extra_args.push(format!("tap-name={host_net_name},mrg-rxbuf"))
96
} else {
97
extra_args.push(format!("tap-name={host_net_name}"))
98
}
99
(None, config.extra_args(extra_args))
100
};
101
102
let guest_vm = TestVm::new_sudo(cfg).expect("fail to create guest vm");
103
if vhost_user_mode {
104
Ok((vu, guest_vm))
105
} else {
106
Ok((None, guest_vm))
107
}
108
}
109
110
/// Configure guest virtio-net device, return virtio name
111
fn network_configure_in_guest(
112
vm: &mut TestVm,
113
host_ip: String,
114
guest_ip: String,
115
) -> anyhow::Result<String> {
116
// mount sysfs to check details
117
vm.exec_in_guest("mount -t sysfs sysfs /sys")
118
.expect("fail to mount sysfs in vm");
119
120
// Parse virtio device list and find out virtio-net id
121
let result = vm
122
.exec_in_guest("cat /sys/bus/virtio/devices/*/device")
123
.expect("Can't get virtio devices id");
124
125
let virtio_id_list: Vec<&str> = result.stdout.split("\n").collect();
126
let virtio_net_id = virtio_id_list.iter().position(|&x| x == "0x0001");
127
// The name of virtio-net driver is virtioX
128
let virtio_name = if let Some(id) = virtio_net_id {
129
format!("virtio{id}")
130
} else {
131
return Err(anyhow!("fail to find virtio net driver"));
132
};
133
134
// Find the ethernet interface name in guest
135
let guest_dev = vm
136
.exec_in_guest(&format!("ls /sys/bus/virtio/devices/{virtio_name}/net"))
137
.expect("Can not find the name of virtio-net")
138
.stdout
139
.trim_end()
140
.to_string();
141
142
// set ip address in guest
143
vm.exec_in_guest(&format!("ip addr add {guest_ip}/24 dev {guest_dev}"))
144
.expect("fail to configure net device address");
145
// up network device
146
vm.exec_in_guest(&format!("ip link set {guest_dev} up"))
147
.expect("fail to up net device");
148
// route information add
149
vm.exec_in_guest(&format!("ip route add default via {host_ip}"))
150
.expect("fail to configure net device address");
151
152
vm.exec_in_guest("ip route show")
153
.expect("fail to configure net device address");
154
Ok(virtio_name)
155
}
156
157
/// Check whether MRG_RXBUF feature bit is configured in guest driver
158
/// vm: guest test VM
159
/// virtio_name: the virtio driver name in guest (e.g. virtio0)
160
fn check_driver_negotiated_features_with_mrg_rxbuf(
161
vm: &mut TestVm,
162
virtio_name: String,
163
) -> anyhow::Result<bool> {
164
let binding = vm
165
.exec_in_guest(&format!(
166
"cat /sys/bus/virtio/devices/{virtio_name}/features"
167
))
168
.expect("Can not get the features of virtio-net");
169
// Find the ethernet interface name in guest
170
let features = binding.stdout.trim_end();
171
172
Ok(features.chars().nth(VIRTIO_NET_F_MRG_RXBUF).unwrap() == '1')
173
}
174
175
fn test_net_connection(
176
config: VmConfig,
177
host_ip: String,
178
host_net_name: String,
179
guest_ip: String,
180
mrg_rxbuf: bool,
181
vhost_user_mode: bool,
182
) -> anyhow::Result<()> {
183
let host_ip_with_mask = format!("{host_ip}/24");
184
let (_vu_device, mut vm) = create_guest_with_virtio_net_backend(
185
config,
186
host_ip_with_mask,
187
host_net_name.clone(),
188
mrg_rxbuf,
189
vhost_user_mode,
190
)
191
.expect("fail to create device and start vm");
192
let virtio_name = network_configure_in_guest(&mut vm, host_ip.clone(), guest_ip.clone())?;
193
194
assert_eq!(
195
mrg_rxbuf,
196
check_driver_negotiated_features_with_mrg_rxbuf(&mut vm, virtio_name)?
197
);
198
let packets_num = "5";
199
let host_ping_guest_result = Command::new("ping")
200
.args([&guest_ip, "-c", packets_num])
201
.output()
202
.expect("fail to ping guest")
203
.stdout;
204
205
assert!(String::from_utf8(host_ping_guest_result)
206
.unwrap()
207
.contains(&format!(
208
"{packets_num} packets transmitted, {packets_num} received"
209
)));
210
let guest_ping_host_result = vm
211
.exec_in_guest(&format!("ping {} -c {}", host_ip.clone(), packets_num))
212
.expect("fail to ping host")
213
.stdout;
214
assert!(guest_ping_host_result.contains(&format!(
215
"{packets_num} packets transmitted, {packets_num} received"
216
)));
217
Command::new("sudo")
218
.args(["ip", "link", "set", &host_net_name.clone(), "down"])
219
.output()
220
.expect("fail to set device down");
221
Ok(())
222
}
223
224
#[test]
225
fn virtio_net_ping_test() -> anyhow::Result<()> {
226
let vm_config = VmConfig::new();
227
test_net_connection(
228
vm_config,
229
"192.168.10.1".to_owned(),
230
"crosvm_tap0".to_owned(),
231
"192.168.10.2".to_owned(),
232
false,
233
false,
234
)?;
235
Ok(())
236
}
237
238
#[test]
239
fn virtio_net_ping_test_with_mrg_rxbuf() -> anyhow::Result<()> {
240
let vm_config = VmConfig::new();
241
test_net_connection(
242
vm_config,
243
"192.168.11.1".to_owned(),
244
"crosvm_tap1".to_owned(),
245
"192.168.11.2".to_owned(),
246
true,
247
false,
248
)?;
249
Ok(())
250
}
251
252
#[test]
253
fn vhost_user_net_ping_test() -> anyhow::Result<()> {
254
let vm_config = VmConfig::new();
255
test_net_connection(
256
vm_config,
257
"192.168.12.1".to_owned(),
258
"crosvm_tap2".to_owned(),
259
"192.168.12.2".to_owned(),
260
false,
261
true,
262
)?;
263
Ok(())
264
}
265
266
#[test]
267
fn vhost_user_net_ping_test_with_mrg_rxbuf() -> anyhow::Result<()> {
268
let vm_config = VmConfig::new();
269
test_net_connection(
270
vm_config,
271
"192.168.13.1".to_owned(),
272
"crosvm_tap3".to_owned(),
273
"192.168.13.2".to_owned(),
274
true,
275
true,
276
)?;
277
Ok(())
278
}
279
280
fn guest_to_host_ncat_test(vm: &mut TestVm, host_ip: String, port: String) -> anyhow::Result<()> {
281
let listen_port = port;
282
let listen_args = vec!["-l", &listen_port];
283
//Create a recv file in host, then ncat listen a port and re-direct to this file
284
let recv_file = File::create("/tmp/host_recv.txt")?;
285
let mut ncat_process = Command::new("ncat")
286
.args(listen_args)
287
.stdout(Stdio::from(recv_file))
288
.spawn()
289
.expect("fail to spawn");
290
291
// create a random file in guest and get the md5sum value of this file
292
vm.exec_in_guest("mount -t tmpfs tmpfs /tmp")
293
.expect("fail to mount tmpfs in vm");
294
295
vm.exec_in_guest("dd if=/dev/random of=/tmp/guest_send.txt bs=1M count=10")
296
.expect("fail to generate a random file");
297
298
let md5_guest = vm
299
.exec_in_guest("md5sum /tmp/guest_send.txt | awk '{ print $1 }'")
300
.expect("fail to check md5sum")
301
.stdout;
302
303
// Transfer this file to host via virtio-net and calculate its md5sum value
304
vm.exec_in_guest(&format!(
305
"ncat {host_ip} {listen_port} < /tmp/guest_send.txt"
306
))
307
.expect("fail to send file");
308
309
ncat_process.wait().expect("failed to wait");
310
311
let res = Command::new("md5sum")
312
.stdout(Stdio::piped())
313
.args(["/tmp/host_recv.txt"])
314
.output()?
315
.stdout;
316
let md5_host = String::from_utf8(res)?
317
.split_whitespace()
318
.next()
319
.unwrap()
320
.to_string();
321
322
assert_eq!(md5_guest.trim_end(), md5_host);
323
324
Ok(())
325
}
326
327
fn host_to_guest_ncat_test(vm: &mut TestVm, guest_ip: String, port: String) -> anyhow::Result<()> {
328
vm.exec_in_guest("mount -t tmpfs tmpfs /tmp")
329
.expect("fail to mount tmpfs in vm");
330
331
//Create a send file in host
332
Command::new("dd")
333
.args([
334
"if=/dev/random",
335
"of=/tmp/host_send.txt",
336
"bs=1M",
337
"count=10",
338
])
339
.output()
340
.expect("fail to generate send file");
341
let res = Command::new("md5sum")
342
.stdout(Stdio::piped())
343
.args(["/tmp/host_send.txt"])
344
.output()?
345
.stdout;
346
let md5_host = String::from_utf8(res)?
347
.split_whitespace()
348
.next()
349
.unwrap()
350
.to_string();
351
let guest_listen_cmd = format!("ncat -l {} > /tmp/guest_recv.txt", port.clone());
352
let guest_cmd = vm.exec_in_guest_async(&guest_listen_cmd).unwrap();
353
354
retry_with_delay(
355
move || {
356
let send_file = File::open("/tmp/host_send.txt")?;
357
let out = Command::new("ncat")
358
.args([&guest_ip, &port])
359
.stdin(Stdio::from(send_file))
360
.output();
361
// if connection refused, it will still return Ok, then retry will exit.
362
if out.as_ref().is_ok_and(|x| {
363
String::from_utf8(x.stderr.clone()).unwrap() != "Ncat: Connection refused.\n"
364
}) {
365
out
366
} else {
367
Err(std::io::Error::other("Ncat: Connection refused"))
368
}
369
},
370
NCAT_RETRIES,
371
NCAT_RETRY_DELAY,
372
)
373
.unwrap();
374
375
guest_cmd.wait_ok(vm).unwrap();
376
let md5_guest = vm
377
.exec_in_guest("md5sum /tmp/guest_recv.txt | awk '{ print $1 }'")
378
.expect("fail to check md5sum")
379
.stdout;
380
assert_eq!(md5_guest.trim_end(), md5_host);
381
Ok(())
382
}
383
384
fn test_ncat_guest_to_host(
385
config: VmConfig,
386
host_ip: String,
387
host_net_name: String,
388
guest_ip: String,
389
mrg_rxbuf: bool,
390
vhost_user_mode: bool,
391
) -> anyhow::Result<()> {
392
let host_ip_with_mask = format!("{host_ip}/24");
393
let (_vu_device, mut vm) = create_guest_with_virtio_net_backend(
394
config,
395
host_ip_with_mask,
396
host_net_name.clone(),
397
mrg_rxbuf,
398
vhost_user_mode,
399
)
400
.expect("fail to create device and start vm");
401
let virtio_name = network_configure_in_guest(&mut vm, host_ip.clone(), guest_ip.clone())?;
402
403
assert_eq!(
404
mrg_rxbuf,
405
check_driver_negotiated_features_with_mrg_rxbuf(&mut vm, virtio_name)?
406
);
407
guest_to_host_ncat_test(&mut vm, host_ip.clone(), "1111".to_owned())?;
408
Ok(())
409
}
410
411
fn test_ncat_host_to_guest(
412
config: VmConfig,
413
host_ip: String,
414
host_net_name: String,
415
guest_ip: String,
416
mrg_rxbuf: bool,
417
vhost_user_mode: bool,
418
) -> anyhow::Result<()> {
419
let host_ip_with_mask = format!("{host_ip}/24");
420
let (_vu_device, mut vm) = create_guest_with_virtio_net_backend(
421
config,
422
host_ip_with_mask,
423
host_net_name.clone(),
424
mrg_rxbuf,
425
vhost_user_mode,
426
)
427
.expect("fail to create device and start vm");
428
let virtio_name = network_configure_in_guest(&mut vm, host_ip.clone(), guest_ip.clone())?;
429
430
assert_eq!(
431
mrg_rxbuf,
432
check_driver_negotiated_features_with_mrg_rxbuf(&mut vm, virtio_name)?
433
);
434
host_to_guest_ncat_test(&mut vm, guest_ip.clone(), "1234".to_owned())?;
435
436
Ok(())
437
}
438
439
#[test]
440
fn vhost_user_net_ncat_test_with_mrg_rxbuf_guest2host() -> anyhow::Result<()> {
441
let vm_config = VmConfig::new();
442
test_ncat_guest_to_host(
443
vm_config,
444
"192.168.14.1".to_owned(),
445
"crosvm_tap4".to_owned(),
446
"192.168.14.2".to_owned(),
447
true,
448
true,
449
)?;
450
Ok(())
451
}
452
453
#[test]
454
fn vhost_user_net_ncat_test_with_mrg_rxbuf_host2guest() -> anyhow::Result<()> {
455
let vm_config = VmConfig::new();
456
test_ncat_host_to_guest(
457
vm_config,
458
"192.168.15.1".to_owned(),
459
"crosvm_tap5".to_owned(),
460
"192.168.15.2".to_owned(),
461
true,
462
true,
463
)?;
464
Ok(())
465
}
466
467