@@ -16,7 +16,10 @@ use core::mem::{ManuallyDrop, MaybeUninit, transmute};
1616
1717use smallvec:: SmallVec ;
1818use 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+ } ;
2023use virtio:: net:: { ConfigVolatileFieldAccess , Hdr , HdrF } ;
2124use virtio:: { DeviceConfigSpace , FeatureBits } ;
2225use volatile:: VolatileRef ;
@@ -291,6 +294,101 @@ impl smoltcp::phy::TxToken for TxToken<'_> {
291294pub 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
296394impl 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