@@ -17,11 +17,12 @@ cfg_select! {
1717use alloc:: boxed:: Box ;
1818use alloc:: vec:: Vec ;
1919use core:: mem:: { ManuallyDrop , MaybeUninit } ;
20+ use core:: num:: NonZeroUsize ;
2021use core:: str:: FromStr ;
2122use core:: { mem, slice} ;
2223
2324use smallvec:: SmallVec ;
24- use smoltcp:: phy:: { Checksum , ChecksumCapabilities , DeviceCapabilities } ;
25+ use smoltcp:: phy:: { Checksum , ChecksumCapabilities , DeviceCapabilities , PacketMeta } ;
2526use smoltcp:: wire:: { ETHERNET_HEADER_LEN , EthernetFrame , Ipv4Packet , Ipv6Packet } ;
2627use virtio:: DeviceConfigSpace ;
2728use virtio:: net:: { ConfigVolatileFieldAccess , Hdr , HdrF } ;
@@ -180,12 +181,23 @@ pub struct TxQueues {
180181 buf_size : u32 ,
181182}
182183
184+ #[ expect(
185+ clippy:: decimal_literal_representation,
186+ reason = "In the VIRTIO specification the size limit is provided in decimal."
187+ ) ]
188+ const MAX_BUFFER_SIZE : usize = 65550 ;
189+
183190impl TxQueues {
184191 pub fn new ( vqs : Vec < VirtQueue > , dev_cfg : & NetDevCfg ) -> Self {
185- Self {
186- vqs,
187- buf_size : determine_mtu ( dev_cfg) . into ( ) ,
188- }
192+ let buf_size = if dev_cfg
193+ . features
194+ . intersects ( virtio:: net:: F :: HOST_TSO4 | virtio:: net:: F :: HOST_TSO6 )
195+ {
196+ MAX_BUFFER_SIZE . try_into ( ) . unwrap ( )
197+ } else {
198+ determine_mtu ( dev_cfg) . into ( )
199+ } ;
200+ Self { vqs, buf_size }
189201 }
190202
191203 #[ allow( dead_code) ]
@@ -253,6 +265,7 @@ pub struct TxToken<'a> {
253265 send_vqs : & ' a mut TxQueues ,
254266 checksums : ChecksumCapabilities ,
255267 send_capacity : & ' a mut u32 ,
268+ meta : PacketMeta ,
256269}
257270
258271impl Drop for TxToken < ' _ > {
@@ -280,17 +293,34 @@ impl smoltcp::phy::TxToken for TxToken<'_> {
280293
281294 let mut header = Box :: new_in ( <Hdr as Default >:: default ( ) , DeviceAlloc ) ;
282295
296+ let is_large_send = cfg_select ! {
297+ feature = "tcp" => token. meta. segmentation_offload_size. is_some( ) ,
298+ _ => false ,
299+ } ;
300+
283301 // If a checksum calculation by the host is necessary, we have to inform the host within the header
284302 // see Virtio specification 5.1.6.2
285303 if let Some ( ( ip_header_len, csum_offset) ) =
286- VirtioNetDriver :: should_request_checksum ( & token. checksums , & mut packet)
304+ VirtioNetDriver :: should_request_checksum ( & token. checksums , & mut packet, is_large_send )
287305 {
288306 header. flags = HdrF :: NEEDS_CSUM ;
289307 header. csum_start =
290308 ( u16:: try_from ( ETHERNET_HEADER_LEN ) . unwrap ( ) + ip_header_len) . into ( ) ;
291309 header. csum_offset = csum_offset. into ( ) ;
292310 }
293311
312+ #[ cfg( feature = "tcp" ) ]
313+ if let Some ( gso_size) = token. meta . segmentation_offload_size {
314+ header. gso_type = match EthernetFrame :: new_unchecked ( & packet) . ethertype ( ) {
315+ smoltcp:: wire:: EthernetProtocol :: Ipv4 => virtio:: net:: HdrGso :: Tcpv4 . into ( ) ,
316+ smoltcp:: wire:: EthernetProtocol :: Ipv6 => virtio:: net:: HdrGso :: Tcpv6 . into ( ) ,
317+ _ => unreachable ! (
318+ "We don't advertise segmentation offload support for any other protocol."
319+ ) ,
320+ } ;
321+ header. gso_size = gso_size. get ( ) . into ( ) ;
322+ }
323+
294324 let buff_tkn = AvailBufferToken :: new (
295325 SmallVec :: from_buf ( [ BufferElem :: Sized ( header) , BufferElem :: Vector ( packet) ] ) ,
296326 SmallVec :: new ( ) ,
@@ -303,6 +333,10 @@ impl smoltcp::phy::TxToken for TxToken<'_> {
303333
304334 result
305335 }
336+
337+ fn set_meta ( & mut self , meta : PacketMeta ) {
338+ self . meta = meta;
339+ }
306340}
307341
308342pub struct RxToken < ' a > {
@@ -436,6 +470,22 @@ impl smoltcp::phy::Device for VirtioNetDriver {
436470 device_capabilities. max_burst_size =
437471 Some ( usize:: try_from ( self . inner . send_capacity ) . unwrap ( ) / usize:: from ( BUFF_PER_PACKET ) ) ;
438472 device_capabilities. checksum = self . checksums . clone ( ) ;
473+
474+ // We only support segmentation offload on and enable the corresponding feature of smoltcp for TCP.
475+ #[ cfg( feature = "tcp" ) ]
476+ {
477+ device_capabilities. segmentation . tcpv4 = self
478+ . dev_cfg
479+ . features
480+ . contains ( virtio:: net:: F :: HOST_TSO4 )
481+ . then_some ( NonZeroUsize :: new ( MAX_BUFFER_SIZE ) . unwrap ( ) ) ;
482+ device_capabilities. segmentation . tcpv6 = self
483+ . dev_cfg
484+ . features
485+ . contains ( virtio:: net:: F :: HOST_TSO6 )
486+ . then_some ( NonZeroUsize :: new ( MAX_BUFFER_SIZE ) . unwrap ( ) ) ;
487+ }
488+
439489 device_capabilities
440490 }
441491
@@ -463,6 +513,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
463513 send_vqs : & mut self . inner . send_vqs ,
464514 checksums : self . checksums . clone ( ) ,
465515 send_capacity : & mut self . inner . send_capacity ,
516+ meta : PacketMeta :: default ( ) ,
466517 } ,
467518 ) )
468519 }
@@ -479,6 +530,7 @@ impl smoltcp::phy::Device for VirtioNetDriver {
479530 send_vqs : & mut self . inner . send_vqs ,
480531 checksums : self . checksums . clone ( ) ,
481532 send_capacity : & mut self . inner . send_capacity ,
533+ meta : PacketMeta :: default ( ) ,
482534 } )
483535 }
484536}
@@ -578,34 +630,40 @@ impl VirtioNetDriver<Init> {
578630 fn should_request_checksum < T : AsRef < [ u8 ] > + AsMut < [ u8 ] > > (
579631 checksums : & ChecksumCapabilities ,
580632 frame : T ,
633+ is_large_send : bool ,
581634 ) -> Option < ( u16 , u16 ) > {
582635 if checksums. tcp . tx ( ) && checksums. udp . tx ( ) {
583636 return None ;
584637 }
585638
586639 let ip_header_len: u16 ;
587- let ip_packet_len: usize ;
588640 let protocol;
589641 let pseudo_header_checksum;
590642 let mut ethernet_frame = EthernetFrame :: new_unchecked ( frame) ;
643+ // We cannot use the `total_len` methods of the packet structures as they read the length
644+ // fields in the headers, which may be set to 0 when the frame is to be segmented and the
645+ // actual length does not fit into the 16 bits of the field.
646+ let ip_packet_len = ethernet_frame. payload_mut ( ) . len ( ) ;
591647 match ethernet_frame. ethertype ( ) {
592648 smoltcp:: wire:: EthernetProtocol :: Ipv4 => {
593649 let ip_packet = Ipv4Packet :: new_unchecked ( & * ethernet_frame. payload_mut ( ) ) ;
594650 ip_header_len = ip_packet. header_len ( ) . into ( ) ;
595- ip_packet_len = ip_packet. total_len ( ) . into ( ) ;
596651 protocol = ip_packet. next_header ( ) ;
597- pseudo_header_checksum =
598- partial_checksum:: ipv4_pseudo_header_partial_checksum ( & ip_packet) ;
652+ pseudo_header_checksum = partial_checksum:: ipv4_pseudo_header_partial_checksum (
653+ & ip_packet,
654+ is_large_send,
655+ ) ;
599656 }
600657 smoltcp:: wire:: EthernetProtocol :: Ipv6 => {
601658 let ip_packet = Ipv6Packet :: new_unchecked ( & * ethernet_frame. payload_mut ( ) ) ;
602659 ip_header_len = ip_packet. header_len ( ) . try_into ( ) . expect (
603660 "VIRTIO does not support IP headers that are longer than u16::MAX bytes." ,
604661 ) ;
605- ip_packet_len = ip_packet. total_len ( ) ;
606662 protocol = ip_packet. next_header ( ) ;
607- pseudo_header_checksum =
608- partial_checksum:: ipv6_pseudo_header_partial_checksum ( & ip_packet) ;
663+ pseudo_header_checksum = partial_checksum:: ipv6_pseudo_header_partial_checksum (
664+ & ip_packet,
665+ is_large_send,
666+ ) ;
609667 }
610668 // If the Ethernet protocol is not one of these two above, for which we know there may be a checksum field,
611669 // we default to not asking for checksum, as otherwise the frame will be corrupted by the device trying
@@ -673,7 +731,9 @@ impl VirtioNetDriver<Uninit> {
673731 // Multiqueue support
674732 | virtio:: net:: F :: MQ
675733 // Checksum calculation can partially be offloaded to the device
676- | virtio:: net:: F :: CSUM ;
734+ | virtio:: net:: F :: CSUM
735+ | virtio:: net:: F :: HOST_TSO4
736+ | virtio:: net:: F :: HOST_TSO6 ;
677737
678738 // Currently the driver does NOT support the features below.
679739 // In order to provide functionality for these, the driver
@@ -949,27 +1009,40 @@ mod partial_checksum {
9491009 /// [RFC 9293 subsection 3.1](https://www.rfc-editor.org/rfc/rfc9293.html#section-3.1-6.18.1) WITHOUT the final inversion.
9501010 pub ( super ) fn ipv4_pseudo_header_partial_checksum < T : AsRef < [ u8 ] > > (
9511011 packet : & Ipv4Packet < T > ,
1012+ is_large_send : bool ,
9521013 ) -> u16 {
9531014 let padded_protocol = u16:: from ( u8:: from ( packet. next_header ( ) ) ) ;
954- let payload_len = packet. total_len ( ) - u16:: from ( packet. header_len ( ) ) ;
955-
9561015 let mut sum = addr_sum ( & packet. src_addr ( ) . octets ( ) ) ;
9571016 sum = ones_complement_add ( sum, addr_sum ( & packet. dst_addr ( ) . octets ( ) ) ) ;
9581017 sum = ones_complement_add ( sum, padded_protocol) ;
959- ones_complement_add ( sum, payload_len)
1018+ if is_large_send {
1019+ // We don't know the packet's length, which will be determined by the device during segmentation.
1020+ // The VIRTIO specification does not specify how partial checksums should be handled for large
1021+ // sends, but excluding the length field is consistent with how
1022+ // [Windows](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/offloading-the-segmentation-of-large-tcp-packets)
1023+ // and [Intel NICs](https://www.intel.com/content/dam/doc/manual/pci-pci-x-family-gbe-controllers-software-dev-manual.pdf)
1024+ // handle this case.
1025+ sum
1026+ } else {
1027+ ones_complement_add ( sum, packet. total_len ( ) - u16:: from ( packet. header_len ( ) ) )
1028+ }
9601029 }
9611030
9621031 /// Calculates the checksum for the IPv6 pseudo-header as described in
9631032 /// [RFC 8200 subsection 8.1](https://www.rfc-editor.org/rfc/rfc8200.html#section-8.1) WITHOUT the final inversion.
9641033 pub ( super ) fn ipv6_pseudo_header_partial_checksum < T : AsRef < [ u8 ] > > (
9651034 packet : & Ipv6Packet < T > ,
1035+ is_large_send : bool ,
9661036 ) -> u16 {
9671037 warn ! ( "The IPv6 partial checksum implementation is untested!" ) ;
9681038 let padded_protocol = u16:: from ( u8:: from ( packet. next_header ( ) ) ) ;
9691039
9701040 let mut sum = addr_sum ( & packet. src_addr ( ) . octets ( ) ) ;
9711041 sum = ones_complement_add ( sum, addr_sum ( & packet. dst_addr ( ) . octets ( ) ) ) ;
972- sum = ones_complement_add ( sum, packet. payload_len ( ) ) ;
1042+ // Refer to the comment related to `is_large_send` in `ipv4_pseudo_header_partial_checksum`.
1043+ if !is_large_send {
1044+ sum = ones_complement_add ( sum, packet. payload_len ( ) ) ;
1045+ }
9731046 ones_complement_add ( sum, padded_protocol)
9741047 }
9751048
0 commit comments