Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/vhost/net.rs
5394 views
1
// Copyright 2017 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
use std::collections::BTreeMap;
6
use std::mem;
7
use std::path::Path;
8
9
use anyhow::anyhow;
10
use anyhow::Context;
11
use base::error;
12
use base::warn;
13
use base::AsRawDescriptor;
14
use base::RawDescriptor;
15
use base::Tube;
16
use base::WorkerThread;
17
use net_util::MacAddress;
18
use net_util::TapT;
19
use vhost::NetT as VhostNetT;
20
use virtio_sys::virtio_config::VIRTIO_F_RING_PACKED;
21
use virtio_sys::virtio_net;
22
use vm_memory::GuestMemory;
23
use zerocopy::IntoBytes;
24
25
use super::control_socket::*;
26
use super::worker::Worker;
27
use super::Error;
28
use super::Result;
29
use crate::pci::MsixStatus;
30
use crate::virtio::copy_config;
31
use crate::virtio::net::build_config;
32
use crate::virtio::DeviceType;
33
use crate::virtio::Interrupt;
34
use crate::virtio::Queue;
35
use crate::virtio::VirtioDevice;
36
use crate::PciAddress;
37
38
const QUEUE_SIZE: u16 = 256;
39
const NUM_QUEUES: usize = 2;
40
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
41
42
pub struct Net<T: TapT + 'static, U: VhostNetT<T> + 'static> {
43
worker_thread: Option<WorkerThread<(Worker<U>, T)>>,
44
tap: Option<T>,
45
guest_mac: Option<[u8; 6]>,
46
vhost_net_handle: Option<U>,
47
avail_features: u64,
48
acked_features: u64,
49
worker_client_tube: Tube,
50
worker_server_tube: Option<Tube>,
51
pci_address: Option<PciAddress>,
52
}
53
54
impl<T, U> Net<T, U>
55
where
56
T: TapT,
57
U: VhostNetT<T>,
58
{
59
/// Creates a new virtio network device from a tap device that has already been
60
/// configured.
61
pub fn new(
62
vhost_net_device_path: &Path,
63
base_features: u64,
64
tap: T,
65
mac_addr: Option<MacAddress>,
66
use_packed_queue: bool,
67
pci_address: Option<PciAddress>,
68
mrg_rxbuf: bool,
69
) -> Result<Net<T, U>> {
70
// Set offload flags to match the virtio features below.
71
tap.set_offload(
72
net_sys::TUN_F_CSUM | net_sys::TUN_F_UFO | net_sys::TUN_F_TSO4 | net_sys::TUN_F_TSO6,
73
)
74
.map_err(Error::TapSetOffload)?;
75
76
// We declare VIRTIO_NET_F_MRG_RXBUF, so set the vnet hdr size to match.
77
let vnet_hdr_size = mem::size_of::<virtio_net::virtio_net_hdr_mrg_rxbuf>();
78
tap.set_vnet_hdr_size(vnet_hdr_size)
79
.map_err(Error::TapSetVnetHdrSize)?;
80
81
let vhost_net_handle = U::new(vhost_net_device_path).map_err(Error::VhostOpen)?;
82
83
let mut avail_features = base_features
84
| 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
85
| 1 << virtio_net::VIRTIO_NET_F_CSUM
86
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
87
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
88
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
89
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
90
| 1 << virtio_net::VIRTIO_NET_F_MRG_RXBUF;
91
92
if use_packed_queue {
93
avail_features |= 1 << VIRTIO_F_RING_PACKED;
94
}
95
96
if mac_addr.is_some() {
97
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
98
}
99
100
if mrg_rxbuf {
101
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MRG_RXBUF;
102
}
103
104
let (worker_client_tube, worker_server_tube) = Tube::pair().map_err(Error::CreateTube)?;
105
106
Ok(Net {
107
worker_thread: None,
108
tap: Some(tap),
109
guest_mac: mac_addr.map(|mac| mac.octets()),
110
vhost_net_handle: Some(vhost_net_handle),
111
avail_features,
112
acked_features: 0u64,
113
worker_client_tube,
114
worker_server_tube: Some(worker_server_tube),
115
pci_address,
116
})
117
}
118
}
119
120
impl<T, U> VirtioDevice for Net<T, U>
121
where
122
T: TapT + 'static,
123
U: VhostNetT<T> + 'static,
124
{
125
fn keep_rds(&self) -> Vec<RawDescriptor> {
126
let mut keep_rds = Vec::new();
127
128
if let Some(tap) = &self.tap {
129
keep_rds.push(tap.as_raw_descriptor());
130
}
131
132
if let Some(vhost_net_handle) = &self.vhost_net_handle {
133
keep_rds.push(vhost_net_handle.as_raw_descriptor());
134
}
135
136
keep_rds.push(self.worker_client_tube.as_raw_descriptor());
137
138
if let Some(worker_server_tube) = &self.worker_server_tube {
139
keep_rds.push(worker_server_tube.as_raw_descriptor());
140
}
141
142
keep_rds
143
}
144
145
fn device_type(&self) -> DeviceType {
146
DeviceType::Net
147
}
148
149
fn queue_max_sizes(&self) -> &[u16] {
150
QUEUE_SIZES
151
}
152
153
fn features(&self) -> u64 {
154
self.avail_features
155
}
156
157
fn ack_features(&mut self, value: u64) {
158
let mut v = value;
159
160
// Check if the guest is ACK'ing a feature that we didn't claim to have.
161
let unrequested_features = v & !self.avail_features;
162
if unrequested_features != 0 {
163
warn!("net: virtio net got unknown feature ack: {:x}", v);
164
165
// Don't count these features as acked.
166
v &= !unrequested_features;
167
}
168
self.acked_features |= v;
169
}
170
171
fn read_config(&self, offset: u64, data: &mut [u8]) {
172
let vq_pairs = QUEUE_SIZES.len() / 2;
173
// VIRTIO_NET_F_MTU is not set.
174
let config_space = build_config(vq_pairs as u16, /* mtu= */ 0, self.guest_mac);
175
copy_config(data, 0, config_space.as_bytes(), offset);
176
}
177
178
fn activate(
179
&mut self,
180
mem: GuestMemory,
181
interrupt: Interrupt,
182
queues: BTreeMap<usize, Queue>,
183
) -> anyhow::Result<()> {
184
if queues.len() != NUM_QUEUES {
185
return Err(anyhow!(
186
"net: expected {} queues, got {}",
187
NUM_QUEUES,
188
queues.len()
189
));
190
}
191
192
let vhost_net_handle = self
193
.vhost_net_handle
194
.take()
195
.context("missing vhost_net_handle")?;
196
let tap = self.tap.take().context("missing tap")?;
197
let acked_features = self.acked_features;
198
let mut worker = Worker::new(
199
"vhost-net",
200
queues,
201
vhost_net_handle,
202
interrupt,
203
acked_features,
204
self.worker_server_tube
205
.take()
206
.expect("worker control tube missing"),
207
mem,
208
None,
209
)
210
.context("net worker init exited with error")?;
211
for idx in 0..NUM_QUEUES {
212
worker
213
.vhost_handle
214
.set_backend(idx, Some(&tap))
215
.map_err(Error::VhostNetSetBackend)?;
216
}
217
self.worker_thread = Some(WorkerThread::start("vhost_net", move |kill_evt| {
218
let result = worker.run(kill_evt);
219
if let Err(e) = result {
220
error!("net worker thread exited with error: {}", e);
221
}
222
for idx in 0..NUM_QUEUES {
223
if let Err(e) = worker.vhost_handle.set_backend(idx, None) {
224
error!("net worker thread failed to clear backend: {:#}", e);
225
}
226
}
227
(worker, tap)
228
}));
229
230
Ok(())
231
}
232
233
fn pci_address(&self) -> Option<PciAddress> {
234
self.pci_address
235
}
236
237
fn on_device_sandboxed(&mut self) {
238
// ignore the error but to log the error. We don't need to do
239
// anything here because when activate, the other vhost set up
240
// will be failed to stop the activate thread.
241
if let Some(vhost_net_handle) = &self.vhost_net_handle {
242
match vhost_net_handle.set_owner() {
243
Ok(_) => {}
244
Err(e) => error!("{}: failed to set owner: {:?}", self.debug_label(), e),
245
}
246
}
247
}
248
249
fn control_notify(&self, behavior: MsixStatus) {
250
if self.worker_thread.is_none() {
251
return;
252
}
253
match behavior {
254
MsixStatus::EntryChanged(index) => {
255
if let Err(e) = self
256
.worker_client_tube
257
.send(&VhostDevRequest::MsixEntryChanged(index))
258
{
259
error!(
260
"{} failed to send VhostMsixEntryChanged request for entry {}: {:?}",
261
self.debug_label(),
262
index,
263
e
264
);
265
return;
266
}
267
if let Err(e) = self.worker_client_tube.recv::<VhostDevResponse>() {
268
error!(
269
"{} failed to receive VhostMsixEntryChanged response for entry {}: {:?}",
270
self.debug_label(),
271
index,
272
e
273
);
274
}
275
}
276
MsixStatus::Changed => {
277
if let Err(e) = self.worker_client_tube.send(&VhostDevRequest::MsixChanged) {
278
error!(
279
"{} failed to send VhostMsixChanged request: {:?}",
280
self.debug_label(),
281
e
282
);
283
return;
284
}
285
if let Err(e) = self.worker_client_tube.recv::<VhostDevResponse>() {
286
error!(
287
"{} failed to receive VhostMsixChanged response {:?}",
288
self.debug_label(),
289
e
290
);
291
}
292
}
293
_ => {}
294
}
295
}
296
297
fn reset(&mut self) -> anyhow::Result<()> {
298
if let Some(worker_thread) = self.worker_thread.take() {
299
let (worker, tap) = worker_thread.stop();
300
self.vhost_net_handle = Some(worker.vhost_handle);
301
self.tap = Some(tap);
302
self.worker_server_tube = Some(worker.server_tube);
303
}
304
Ok(())
305
}
306
}
307
308
#[cfg(test)]
309
pub mod tests {
310
use std::net::Ipv4Addr;
311
use std::path::PathBuf;
312
use std::result;
313
314
use base::pagesize;
315
use base::Event;
316
use hypervisor::ProtectionType;
317
use net_util::sys::linux::fakes::FakeTap;
318
use net_util::TapTCommon;
319
use vhost::net::fakes::FakeNet;
320
use vm_memory::GuestAddress;
321
use vm_memory::GuestMemory;
322
use vm_memory::GuestMemoryError;
323
324
use super::*;
325
use crate::virtio::base_features;
326
use crate::virtio::QueueConfig;
327
328
fn create_guest_memory() -> result::Result<GuestMemory, GuestMemoryError> {
329
let start_addr1 = GuestAddress(0x0);
330
let start_addr2 = GuestAddress(pagesize() as u64);
331
GuestMemory::new(&[
332
(start_addr1, pagesize() as u64),
333
(start_addr2, 4 * pagesize() as u64),
334
])
335
}
336
337
fn create_net_common() -> Net<FakeTap, FakeNet<FakeTap>> {
338
let tap = FakeTap::new(true, false).unwrap();
339
tap.set_ip_addr(Ipv4Addr::new(127, 0, 0, 1))
340
.map_err(Error::TapSetIp)
341
.unwrap();
342
tap.set_netmask(Ipv4Addr::new(255, 255, 255, 0))
343
.map_err(Error::TapSetNetmask)
344
.unwrap();
345
let mac = "de:21:e8:47:6b:6a".parse().unwrap();
346
tap.set_mac_address(mac).unwrap();
347
tap.enable().unwrap();
348
349
let features = base_features(ProtectionType::Unprotected);
350
Net::<FakeTap, FakeNet<FakeTap>>::new(
351
&PathBuf::from(""),
352
features,
353
tap,
354
Some(mac),
355
false,
356
None,
357
false,
358
)
359
.unwrap()
360
}
361
362
#[test]
363
fn create_net() {
364
create_net_common();
365
}
366
367
#[test]
368
fn keep_rds() {
369
let net = create_net_common();
370
let fds = net.keep_rds();
371
assert!(
372
!fds.is_empty(),
373
"We should have gotten at least one descriptor"
374
);
375
}
376
377
#[test]
378
fn features() {
379
let net = create_net_common();
380
// Feature bits 0-23 and 50-127 are specific for the device type, but
381
// at the moment crosvm only supports 64 bits of feature bits.
382
const DEVICE_FEATURE_BITS: u64 = 0xffffff;
383
let expected_features = 1 << 0 // VIRTIO_NET_F_CSUM
384
| 1 << 1 // VIRTIO_NET_F_GUEST_CSUM
385
| 1 << 5 // VIRTIO_NET_F_MAC
386
| 1 << 7 // VIRTIO_NET_F_GUEST_TSO4
387
| 1 << 10 // VIRTIO_NET_F_GUEST_UFO
388
| 1 << 11 // VIRTIO_NET_F_HOST_TSO4
389
| 1 << 14 // VIRTIO_NET_F_HOST_UFO
390
| 1 << 15; // VIRTIO_NET_F_MRG_RXBUF
391
assert_eq!(net.features() & DEVICE_FEATURE_BITS, expected_features);
392
}
393
394
#[test]
395
fn ack_features() {
396
let mut net = create_net_common();
397
// Just testing that we don't panic, for now
398
net.ack_features(1);
399
net.ack_features(1 << 32);
400
}
401
402
#[test]
403
fn activate() {
404
let mut net = create_net_common();
405
let guest_memory = create_guest_memory().unwrap();
406
let interrupt = Interrupt::new_for_test();
407
408
let mut q0 = QueueConfig::new(1, 0);
409
q0.set_ready(true);
410
let q0 = q0
411
.activate(&guest_memory, Event::new().unwrap(), interrupt.clone())
412
.expect("QueueConfig::activate");
413
414
let mut q1 = QueueConfig::new(1, 0);
415
q1.set_ready(true);
416
let q1 = q1
417
.activate(&guest_memory, Event::new().unwrap(), interrupt.clone())
418
.expect("QueueConfig::activate");
419
420
// Just testing that we don't panic, for now
421
let _ = net.activate(guest_memory, interrupt, BTreeMap::from([(0, q0), (1, q1)]));
422
}
423
}
424
425