diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index d390aacc4e..99bce67ea4 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -379,6 +379,7 @@ where destination, contents: buf, ecn: None, + segment_size: None, }); } } @@ -559,6 +560,7 @@ where } else { None }, + segment_size: None, }) } diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 8717dd4c4c..151724818c 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -189,6 +189,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); return None; } @@ -334,6 +335,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); } @@ -587,6 +589,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); return None; } @@ -679,6 +682,7 @@ where destination, ecn: None, contents: buf, + segment_size: None, }) } diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 3c13a05d3b..d34ace846a 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -255,6 +255,9 @@ pub struct Transmit { pub ecn: Option, /// Contents of the datagram pub contents: Vec, + /// The segment size if this transmission contains multiple datagrams. + /// This is `None` if the transmit only contains a single datagram + pub segment_size: Option, } // diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 177bef0023..9ebed5f7f5 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -38,6 +38,9 @@ tracing = "0.1.10" tokio = { version = "0.2.6", features = ["rt-core", "io-driver", "time"] } webpki = { version = "0.21", optional = true } +[target.'cfg(unix)'.dependencies] +lazy_static = "1" + [dev-dependencies] anyhow = "1.0.22" crc = "1.8.1" diff --git a/quinn/src/platform/fallback.rs b/quinn/src/platform/fallback.rs index a1d129276b..097a8f8bac 100644 --- a/quinn/src/platform/fallback.rs +++ b/quinn/src/platform/fallback.rs @@ -44,4 +44,9 @@ impl super::UdpExt for UdpSocket { } } +/// Returns the platforms UDP socket capabilities +pub fn caps() -> super::UdpCapabilities { + super::UdpCapabilities { gso: false } +} + pub const BATCH_SIZE: usize = 1; diff --git a/quinn/src/platform/mod.rs b/quinn/src/platform/mod.rs index bdf77d1fd4..7bd51c4d02 100644 --- a/quinn/src/platform/mod.rs +++ b/quinn/src/platform/mod.rs @@ -15,6 +15,12 @@ mod imp; #[path = "fallback.rs"] mod imp; +#[allow(dead_code)] // TODO: Remove when used +/// Returns the platforms UDP socket capabilities +pub fn caps() -> UdpCapabilities { + imp::caps() +} + /// Number of UDP packets to send/receive at a time pub const BATCH_SIZE: usize = imp::BATCH_SIZE; @@ -23,3 +29,10 @@ pub trait UdpExt { fn send_ext(&self, transmits: &[Transmit]) -> io::Result; fn recv_ext(&self, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result; } + +/// The capabilities a UDP socket suppports on a certain platform +#[derive(Debug, Clone, Copy)] +pub struct UdpCapabilities { + /// Whether the platform supports Generic Send Offload (GSO) + pub gso: bool, +} diff --git a/quinn/src/platform/unix.rs b/quinn/src/platform/unix.rs index 5257985b6d..e374bf2f8e 100644 --- a/quinn/src/platform/unix.rs +++ b/quinn/src/platform/unix.rs @@ -7,10 +7,11 @@ use std::{ ptr, }; +use lazy_static::lazy_static; use mio::net::UdpSocket; use proto::{EcnCodepoint, Transmit}; -use super::cmsg; +use super::{cmsg, UdpCapabilities, UdpExt}; use crate::udp::RecvMeta; #[cfg(target_os = "freebsd")] @@ -18,7 +19,7 @@ type IpTosTy = libc::c_uchar; #[cfg(not(target_os = "freebsd"))] type IpTosTy = libc::c_int; -impl super::UdpExt for UdpSocket { +impl UdpExt for UdpSocket { fn init_ext(&self) -> io::Result<()> { // Safety assert_eq!( @@ -267,6 +268,11 @@ impl super::UdpExt for UdpSocket { } } +/// Returns the platforms UDP socket capabilities +pub fn caps() -> UdpCapabilities { + *CAPABILITIES +} + const CMSG_LEN: usize = 64; fn prepare_msg( @@ -296,6 +302,26 @@ fn prepare_msg( } else { encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); } + + if let Some(segment_size) = transmit.segment_size { + debug_assert!( + caps().gso, + "Platform must support GSO for setting segment size" + ); + + #[cfg(target_os = "linux")] + fn set_segment_size(encoder: &mut cmsg::Encoder, segment_size: u16) { + encoder.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size); + } + + #[cfg(not(target_os = "linux"))] + fn set_segment_size(_encoder: &mut cmsg::Encoder, _segment_size: u16) { + panic!("Setting a segment size is not supported on current platform"); + } + + set_segment_size(&mut encoder, segment_size as u16); + } + encoder.finish(); } @@ -373,3 +399,40 @@ pub const BATCH_SIZE: usize = 32; #[cfg(any(target_os = "macos", target_os = "ios"))] pub const BATCH_SIZE: usize = 1; + +#[cfg(target_os = "linux")] +lazy_static! { + static ref CAPABILITIES: UdpCapabilities = { + /// Checks whether GSO support is available by setting the UDP_SEGMENT + /// option on a socket + fn supports_gso() -> bool { + const GSO_SIZE: libc::c_int = 1500; + + let socket = match std::net::UdpSocket::bind("[::]:0") { + Ok(socket) => socket, + Err(_) => return false, + }; + + let rc = unsafe { + libc::setsockopt( + socket.as_raw_fd(), + libc::SOL_UDP, + libc::UDP_SEGMENT, + &GSO_SIZE as *const _ as _, + mem::size_of_val(&GSO_SIZE) as _, + ) + }; + + rc != -1 + } + + UdpCapabilities { + gso: supports_gso(), + } + }; +} + +#[cfg(not(target_os = "linux"))] +lazy_static! { + pub static ref CAPABILITIES: UdpCapabilities = UdpCapabilities { gso: false }; +}