From 15a34bee7cc029545abde6f3346ebab7f01a271a Mon Sep 17 00:00:00 2001 From: Matthias Einwag Date: Thu, 31 Dec 2020 15:12:31 -0800 Subject: [PATCH] GSO platform support This splits out the platform/socket parts for UDP GSO support from #953 to reduce the amount of code to review. This change mainly implements setting the segment size socket option, and adds a runtime detection mechanism for GSO support. --- quinn-proto/src/connection/mod.rs | 2 + quinn-proto/src/endpoint.rs | 4 ++ quinn-proto/src/lib.rs | 3 ++ quinn/Cargo.toml | 3 ++ quinn/src/platform/fallback.rs | 4 ++ quinn/src/platform/mod.rs | 8 ++++ quinn/src/platform/unix.rs | 70 +++++++++++++++++++++++++++++-- 7 files changed, 91 insertions(+), 3 deletions(-) diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index 7acef9501e..c02e82e0e1 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -388,6 +388,7 @@ where destination, contents: buf, ecn: None, + segment_size: None, }); } } @@ -568,6 +569,7 @@ where } else { None }, + segment_size: None, }) } diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index b55b48219f..de5ae27f01 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -191,6 +191,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); return None; } @@ -336,6 +337,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); } @@ -584,6 +586,7 @@ where destination: remote, ecn: None, contents: buf, + segment_size: None, }); return None; } @@ -677,6 +680,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..c1212211dc 100644 --- a/quinn/src/platform/fallback.rs +++ b/quinn/src/platform/fallback.rs @@ -42,6 +42,10 @@ impl super::UdpExt for UdpSocket { }; Ok(1) } + + 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..ccf0fbfb4a 100644 --- a/quinn/src/platform/mod.rs +++ b/quinn/src/platform/mod.rs @@ -22,4 +22,12 @@ pub trait UdpExt { fn init_ext(&self) -> io::Result<()>; fn send_ext(&self, transmits: &[Transmit]) -> io::Result; fn recv_ext(&self, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result; + fn caps() -> UdpCapabilities; +} + +/// 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..6ec8a6dd57 100644 --- a/quinn/src/platform/unix.rs +++ b/quinn/src/platform/unix.rs @@ -7,6 +7,7 @@ use std::{ ptr, }; +use lazy_static::lazy_static; use mio::net::UdpSocket; use proto::{EcnCodepoint, Transmit}; @@ -20,6 +21,8 @@ type IpTosTy = libc::c_int; impl super::UdpExt for UdpSocket { fn init_ext(&self) -> io::Result<()> { + Self::caps(); + // Safety assert_eq!( mem::size_of::(), @@ -148,7 +151,7 @@ impl super::UdpExt for UdpSocket { let mut iovecs: [libc::iovec; BATCH_SIZE] = unsafe { mem::zeroed() }; let mut cmsgs = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; for (i, transmit) in transmits.iter().enumerate().take(BATCH_SIZE) { - prepare_msg( + prepare_msg::( transmit, &mut msgs[i].msg_hdr, &mut iovecs[i], @@ -182,7 +185,7 @@ impl super::UdpExt for UdpSocket { let mut ctrl = cmsg::Aligned([0u8; CMSG_LEN]); let mut sent = 0; while sent < transmits.len() { - prepare_msg(&transmits[sent], &mut hdr, &mut iov, &mut ctrl); + prepare_msg::(&transmits[sent], &mut hdr, &mut iov, &mut ctrl); let n = unsafe { libc::sendmsg(self.as_raw_fd(), &hdr, 0) }; if n == -1 { let e = io::Error::last_os_error(); @@ -265,11 +268,15 @@ impl super::UdpExt for UdpSocket { meta[0] = decode_recv(&name, &hdr, n as usize); Ok(1) } + + fn caps() -> super::UdpCapabilities { + *CAPABILITIES + } } const CMSG_LEN: usize = 64; -fn prepare_msg( +fn prepare_msg( transmit: &Transmit, hdr: &mut libc::msghdr, iov: &mut libc::iovec, @@ -296,6 +303,26 @@ fn prepare_msg( } else { encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); } + + if let Some(segment_size) = transmit.segment_size { + debug_assert!( + U::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 +400,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! { + pub static ref CAPABILITIES: super::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 + } + + super::UdpCapabilities { + gso: supports_gso(), + } + }; +} + +#[cfg(not(target_os = "linux"))] +lazy_static! { + pub static ref CAPABILITIES: super::UdpCapabilities = super::UdpCapabilities { gso: false }; +}