Skip to content

Commit 5dfa59a

Browse files
cagatay-ymkroening
authored andcommitted
feature(virtio-net): VIRTIO_NET_F_GUEST_CSUM support
1 parent 72feee1 commit 5dfa59a

File tree

1 file changed

+121
-15
lines changed

1 file changed

+121
-15
lines changed

src/drivers/net/virtio/mod.rs

Lines changed: 121 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ use core::mem::{ManuallyDrop, MaybeUninit, transmute};
1616

1717
use smallvec::SmallVec;
1818
use smoltcp::phy::{Checksum, ChecksumCapabilities, DeviceCapabilities};
19-
use smoltcp::wire::{ETHERNET_HEADER_LEN, EthernetFrame, Ipv4Packet, Ipv6Packet};
19+
use smoltcp::wire::{
20+
ETHERNET_HEADER_LEN, EthernetFrame, IpAddress, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket,
21+
UdpPacket,
22+
};
2023
use virtio::net::{ConfigVolatileFieldAccess, Hdr, HdrF};
2124
use virtio::{DeviceConfigSpace, FeatureBits};
2225
use volatile::VolatileRef;
@@ -291,6 +294,101 @@ impl smoltcp::phy::TxToken for TxToken<'_> {
291294
pub struct RxToken<'a> {
292295
recv_vqs: &'a mut RxQueues,
293296
is_mrg_rxbuf_enabled: bool,
297+
checksums: ChecksumCapabilities,
298+
}
299+
300+
impl RxToken<'_> {
301+
/// If we advertised receive checksum offload to smoltcp, we need to validate the packet
302+
/// either by checking its virtio-net headers or checksum. Otherwise, it's smoltcp's responsibility
303+
/// to validate the frame and we can pass the frame directly.
304+
fn is_ethernet_frame_passable(&self, hdr: &Hdr, frame: &[u8]) -> bool {
305+
// Nothing is offloaded to the device. We can pass the frame right off to smoltcp.
306+
if self.checksums.tcp.rx() && self.checksums.udp.rx() {
307+
return true;
308+
}
309+
310+
let Ok(ethernet_frame) = EthernetFrame::new_checked(frame) else {
311+
return false;
312+
};
313+
314+
// We are receiving a frame that was sent by another virtio-net driver on the same host.
315+
// Normally, the device should have filled in the checksum but passed the buffers right along
316+
// instead as checksumming is not necessary for two guests on the same host.
317+
if hdr.flags.contains(virtio::net::HdrF::NEEDS_CSUM) {
318+
return true;
319+
}
320+
321+
// We cannot benefit from the same host optimization but we've promised smoltcp to only pass frames
322+
// that are validated so we need to do the validation ourselves.
323+
match ethernet_frame.ethertype() {
324+
smoltcp::wire::EthernetProtocol::Ipv4 => {
325+
let Ok(ip_packet) = Ipv4Packet::new_checked(ethernet_frame.payload()) else {
326+
return false;
327+
};
328+
329+
// DATA_VALID only validates the outermost packet checksum, which is IPv4 in this case. Thus,
330+
// it does not save us from validating the layer above IP.
331+
Self::is_ip_packet_passable(
332+
ip_packet.next_header(),
333+
ip_packet.payload(),
334+
IpAddress::Ipv4(ip_packet.src_addr()),
335+
IpAddress::Ipv4(ip_packet.dst_addr()),
336+
&self.checksums,
337+
)
338+
}
339+
smoltcp::wire::EthernetProtocol::Ipv6 => {
340+
let Ok(ip_packet) = Ipv6Packet::new_checked(ethernet_frame.payload()) else {
341+
return false;
342+
};
343+
// One level of checksum has been validated and IPv6 headers don't have their own checksums,
344+
// so the validation from the device must have been for the IP protocol.
345+
hdr.flags.contains(virtio::net::HdrF::DATA_VALID) || Self::is_ip_packet_passable(
346+
ip_packet.next_header(),
347+
ip_packet.payload(),
348+
IpAddress::Ipv6(ip_packet.src_addr()),
349+
IpAddress::Ipv6(ip_packet.dst_addr()),
350+
&self.checksums,
351+
)
352+
}
353+
// ARP packets don't have checksums.
354+
smoltcp::wire::EthernetProtocol::Arp
355+
// We should have not taken over the validation of any unknown protocol from smoltcp and may let
356+
// it take care of it.
357+
| smoltcp::wire::EthernetProtocol::Unknown(_) => {
358+
true
359+
}
360+
}
361+
}
362+
363+
fn is_ip_packet_passable(
364+
next_header: IpProtocol,
365+
payload: &[u8],
366+
src_addr: IpAddress,
367+
dst_addr: IpAddress,
368+
checksum_capabilities: &ChecksumCapabilities,
369+
) -> bool {
370+
match next_header {
371+
smoltcp::wire::IpProtocol::Tcp => {
372+
if checksum_capabilities.tcp.rx() {
373+
return true;
374+
}
375+
let Ok(packet) = TcpPacket::new_checked(payload) else {
376+
return false;
377+
};
378+
packet.verify_checksum(&src_addr, &dst_addr)
379+
}
380+
smoltcp::wire::IpProtocol::Udp => {
381+
if checksum_capabilities.udp.rx() {
382+
return true;
383+
}
384+
let Ok(packet) = UdpPacket::new_checked(payload) else {
385+
return false;
386+
};
387+
packet.verify_checksum(&src_addr, &dst_addr)
388+
}
389+
_ => true,
390+
}
391+
}
294392
}
295393

296394
impl smoltcp::phy::RxToken for RxToken<'_> {
@@ -321,18 +419,6 @@ impl smoltcp::phy::RxToken for RxToken<'_> {
321419
};
322420

323421
let mut combined_packets = first_packet;
324-
325-
let first_tkn = buffer_token_from_hdr(
326-
// SAFETY: Box<T> -> Box<MaybeUninit<T>> is sound
327-
unsafe {
328-
transmute::<Box<Hdr, DeviceAlloc>, Box<MaybeUninit<Hdr>, DeviceAlloc>>(first_header)
329-
},
330-
self.recv_vqs.buf_size,
331-
);
332-
self.recv_vqs.vqs[0]
333-
.dispatch(first_tkn, false, BufferType::Direct)
334-
.unwrap();
335-
336422
for _ in 1..num_buffers {
337423
let mut buffer_tkn = self.recv_vqs.get_next().unwrap();
338424
// The descriptor that was meant for the header of another frame was used for a portion of the current frame's contents.
@@ -357,7 +443,24 @@ impl smoltcp::phy::RxToken for RxToken<'_> {
357443
.unwrap();
358444
}
359445

360-
f(&combined_packets)
446+
let res = if self.is_ethernet_frame_passable(&first_header, &combined_packets) {
447+
f(&combined_packets)
448+
} else {
449+
f(&[])
450+
};
451+
452+
let first_tkn = buffer_token_from_hdr(
453+
// SAFETY: Box<T> -> Box<MaybeUninit<T>> is sound
454+
unsafe {
455+
transmute::<Box<Hdr, DeviceAlloc>, Box<MaybeUninit<Hdr>, DeviceAlloc>>(first_header)
456+
},
457+
self.recv_vqs.buf_size,
458+
);
459+
self.recv_vqs.vqs[0]
460+
.dispatch(first_tkn, false, BufferType::Direct)
461+
.unwrap();
462+
463+
res
361464
}
362465
}
363466

@@ -433,6 +536,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
433536
RxToken {
434537
recv_vqs: &mut self.inner.recv_vqs,
435538
is_mrg_rxbuf_enabled: self.dev_cfg.features.contains(virtio::net::F::MRG_RXBUF),
539+
checksums: self.checksums.clone(),
436540
},
437541
TxToken {
438542
send_vqs: &mut self.inner.send_vqs,
@@ -650,7 +754,9 @@ impl VirtioNetDriver<Uninit> {
650754
// Multiqueue support
651755
| virtio::net::F::MQ
652756
// Checksum calculation can partially be offloaded to the device
653-
| virtio::net::F::CSUM;
757+
| virtio::net::F::CSUM
758+
// Partially checksummed frames can be received
759+
| virtio::net::F::GUEST_CSUM;
654760

655761
// Currently the driver does NOT support the features below.
656762
// In order to provide functionality for these, the driver

0 commit comments

Comments
 (0)