@@ -797,18 +797,30 @@ pub(crate) const BATCH_SIZE: usize = 1;
797797#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
798798mod gso {
799799 use super :: * ;
800+ use std:: { ffi:: CStr , mem, str:: FromStr , sync:: OnceLock } ;
800801
801802 #[ cfg( not( target_os = "android" ) ) ]
802803 const UDP_SEGMENT : libc:: c_int = libc:: UDP_SEGMENT ;
803804 #[ cfg( target_os = "android" ) ]
804805 // TODO: Add this to libc
805806 const UDP_SEGMENT : libc:: c_int = 103 ;
806807
807- /// Checks whether GSO support is available by setting the UDP_SEGMENT
808- /// option on a socket
808+ // Support for UDP GSO has been added to linux kernel in version 4.18
809+ // https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645
810+ const SUPPORTED_SINCE : KernelVersion = KernelVersion {
811+ version : 4 ,
812+ major_revision : 18 ,
813+ } ;
814+
815+ /// Checks whether GSO support is available by checking the kernel version followed by setting
816+ /// the UDP_SEGMENT option on a socket
809817 pub ( crate ) fn max_gso_segments ( ) -> usize {
810818 const GSO_SIZE : libc:: c_int = 1500 ;
811819
820+ if !SUPPORTED_BY_CURRENT_KERNEL . get_or_init ( supported_by_current_kernel) {
821+ return 1 ;
822+ }
823+
812824 let socket = match std:: net:: UdpSocket :: bind ( "[::]:0" )
813825 . or_else ( |_| std:: net:: UdpSocket :: bind ( ( Ipv4Addr :: LOCALHOST , 0 ) ) )
814826 {
@@ -833,6 +845,116 @@ mod gso {
833845 pub ( crate ) fn set_segment_size ( encoder : & mut cmsg:: Encoder < libc:: msghdr > , segment_size : u16 ) {
834846 encoder. push ( libc:: SOL_UDP , UDP_SEGMENT , segment_size) ;
835847 }
848+
849+ // Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`.
850+ static SUPPORTED_BY_CURRENT_KERNEL : OnceLock < bool > = OnceLock :: new ( ) ;
851+
852+ fn supported_by_current_kernel ( ) -> bool {
853+ let kernel_version_string = match kernel_version_string ( ) {
854+ Ok ( kernel_version_string) => kernel_version_string,
855+ Err ( _e) => {
856+ crate :: log:: warn!( "GSO disabled: uname returned {_e}" ) ;
857+ return false ;
858+ }
859+ } ;
860+
861+ let Some ( kernel_version) = KernelVersion :: from_str ( & kernel_version_string) else {
862+ crate :: log:: warn!(
863+ "GSO disabled: failed to parse kernel version ({kernel_version_string})"
864+ ) ;
865+ return false ;
866+ } ;
867+
868+ if kernel_version < SUPPORTED_SINCE {
869+ crate :: log:: info!( "GSO disabled: kernel too old ({kernel_version_string}); need 4.18+" , ) ;
870+ return false ;
871+ }
872+
873+ true
874+ }
875+
876+ fn kernel_version_string ( ) -> io:: Result < String > {
877+ let mut n = unsafe { mem:: zeroed ( ) } ;
878+ let r = unsafe { libc:: uname ( & mut n) } ;
879+ if r != 0 {
880+ return Err ( io:: Error :: last_os_error ( ) ) ;
881+ }
882+ Ok ( unsafe {
883+ CStr :: from_ptr ( n. release [ ..] . as_ptr ( ) )
884+ . to_string_lossy ( )
885+ . into_owned ( )
886+ } )
887+ }
888+
889+ // https://www.linfo.org/kernel_version_numbering.html
890+ #[ derive( Eq , PartialEq , Ord , PartialOrd , Debug ) ]
891+ struct KernelVersion {
892+ version : u8 ,
893+ major_revision : u8 ,
894+ }
895+
896+ impl KernelVersion {
897+ fn from_str ( release : & str ) -> Option < Self > {
898+ let mut split = release
899+ . split_once ( '-' )
900+ . map ( |pair| pair. 0 )
901+ . unwrap_or ( release)
902+ . split ( '.' ) ;
903+
904+ let version = u8:: from_str ( split. next ( ) ?) . ok ( ) ?;
905+ let major_revision = u8:: from_str ( split. next ( ) ?) . ok ( ) ?;
906+
907+ Some ( Self {
908+ version,
909+ major_revision,
910+ } )
911+ }
912+ }
913+
914+ #[ cfg( test) ]
915+ mod test {
916+ use super :: * ;
917+
918+ #[ test]
919+ fn parse_current_kernel_version_release_string ( ) {
920+ let release = kernel_version_string ( ) . unwrap ( ) ;
921+ KernelVersion :: from_str ( & release) . unwrap ( ) ;
922+ }
923+
924+ #[ test]
925+ fn parse_kernel_version_release_string ( ) {
926+ // These are made up for the test
927+ assert_eq ! (
928+ KernelVersion :: from_str( "4.14" ) ,
929+ Some ( KernelVersion {
930+ version: 4 ,
931+ major_revision: 14
932+ } )
933+ ) ;
934+ assert_eq ! (
935+ KernelVersion :: from_str( "4.18" ) ,
936+ Some ( KernelVersion {
937+ version: 4 ,
938+ major_revision: 18
939+ } )
940+ ) ;
941+ // These were seen in the wild
942+ assert_eq ! (
943+ KernelVersion :: from_str( "4.14.186-27095505" ) ,
944+ Some ( KernelVersion {
945+ version: 4 ,
946+ major_revision: 14
947+ } )
948+ ) ;
949+ assert_eq ! (
950+ KernelVersion :: from_str( "6.8.0-59-generic" ) ,
951+ Some ( KernelVersion {
952+ version: 6 ,
953+ major_revision: 8
954+ } )
955+ ) ;
956+ }
957+ }
836958}
837959
838960// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not
0 commit comments