Skip to main content

tokio_fast_udp/
tokio_fast_udp.rs

1//! Fast Unix UDP I/O for tokio.
2//!
3//! Provides async UDP sockets that leverage modern OS syscalls for high
4//! throughput:
5//!
6//! - **GSO** (Generic Segmentation Offload) on Linux: send a single large
7//!   buffer that the kernel/NIC splits into multiple UDP datagrams.
8//! - **GRO** (Generic Receive Offload) on Linux: receive multiple coalesced
9//!   datagrams in one syscall.
10//! - **ECN** (Explicit Congestion Notification): send and receive IP-level
11//!   congestion marks.
12//! - **SO_REUSEPORT** (Linux): bind multiple sockets to the same address:port
13//!   for kernel-level load balancing across processes/threads.
14//!
15//! On platforms without these optimizations (macOS, iOS, etc.) a
16//! portable `sendmsg`/`recvmsg` fallback is used so the same application code
17//! compiles and runs across Unix targets.
18//!
19//! # Quick start
20//!
21//! ```no_run
22//! use tokio_fast_udp::{FastUdpSocket, ReceiveItem, SendItem};
23//!
24//! #[tokio::main]
25//! async fn main() -> std::io::Result<()> {
26//!     let socket = FastUdpSocket::build("127.0.0.1:9000".parse().unwrap())
27//!         .bind()?;
28//!
29//!     let dst = "127.0.0.1:9001".parse().unwrap();
30//!     let items = [SendItem::new(dst, b"hello")];
31//!     socket.send_many(&items).await?;
32//!
33//!     let mut buf = vec![0u8; 1500];
34//!     let mut recv = [ReceiveItem::new(&mut buf)];
35//!     socket.receive_many(&mut recv).await?;
36//!     println!("got {} bytes", recv[0].len());
37//!     Ok(())
38//! }
39//! ```
40//!
41//! # Sending many datagrams in one call
42//!
43//! On Linux, a single [`SendItem`] with a segment size is sent as one
44//! `sendmsg` plus `UDP_SEGMENT`, and the kernel or NIC performs the
45//! segmentation (GSO). On platforms without GSO the payload is split into one
46//! datagram per segment in user space, so the same code runs everywhere.
47//!
48//! ```no_run
49//! use tokio_fast_udp::{FastUdpSocket, SendItem};
50//!
51//! #[tokio::main]
52//! async fn main() -> std::io::Result<()> {
53//!     let socket = FastUdpSocket::build("0.0.0.0:0".parse().unwrap()).bind()?;
54//!     let segment_size = 1232; // a typical QUIC max datagram
55//!     let payload = vec![0u8; segment_size * 64]; // 64 datagrams back-to-back
56//!     let dst = "127.0.0.1:9001".parse().unwrap();
57//!
58//!     let items = [SendItem::new(dst, &payload).segment_size(segment_size as u16)];
59//!     socket.send_many(&items).await?;
60//!     Ok(())
61//! }
62//! ```
63//!
64//! # SO_REUSEPORT
65//!
66//! On Linux, multiple sockets can bind the same address and port, with the
67//! kernel distributing incoming datagrams across them:
68//!
69//! ```no_run
70//! use tokio_fast_udp::FastUdpSocket;
71//!
72//! fn main() -> std::io::Result<()> {
73//!     let socket = FastUdpSocket::build("0.0.0.0:9000".parse().unwrap())
74//!         .reuse_port(true)
75//!         .bind()?;
76//!     Ok(())
77//! }
78//! ```
79//!
80//! `SO_REUSEPORT` is a no-op on platforms that don't support it.
81//!
82//! # Kill switches
83//!
84//! On Unix, each optimization can be turned on or off through the builder,
85//! without feature flags:
86//!
87//! ```no_run
88//! use tokio_fast_udp::FastUdpSocket;
89//!
90//! fn main() -> std::io::Result<()> {
91//!     let socket = FastUdpSocket::build("0.0.0.0:9000".parse().unwrap())
92//!         .gso(false) // no segmentation offload on send
93//!         .gro(false) // no coalescing on receive
94//!         .ecn(false) // no ECN ancillary data
95//!         .max_batch_size(64)
96//!         .bind()?;
97//!     Ok(())
98//! }
99//! ```
100//!
101//! # Platform support
102//!
103//! | Platform | GSO | GRO | ECN | ReusePort | Batch send | Batch recv |
104//! |----------|-----|-----|-----|-----------|------------|------------|
105//! | Linux    | yes | yes | yes | yes       | sendmmsg | recv_gro |
106//! | macOS    | —   | —   | yes | yes       | sendmsg loop | recvmsg loop |
107//! | iOS      | —   | —   | yes | yes       | sendmsg loop | recvmsg loop |
108//!
109//! **Linux** is the primary fast path. GSO and GRO are probed at build time and
110//! gracefully disabled if the kernel doesn't support them.
111//!
112//! **macOS, iOS and other Unix** platforms use `sendmsg`/`recvmsg` with ECN
113//! ancillary data where supported. Segmented sends (`SendItem::segment_size`)
114//! are emulated there by splitting the payload into one datagram per segment.
115//!
116//! # API
117//!
118//! ```text
119//! impl FastUdpSocket {
120//!     pub fn build(addr: SocketAddr) -> FastUdpSocketBuilder;
121//!     pub async fn send(&self, item: SendItem<'_>) -> io::Result<()>;
122//!     pub async fn send_many(&self, items: &[SendItem<'_>]) -> io::Result<usize>;
123//!     pub async fn receive(&self, item: &mut ReceiveItem<'_>) -> io::Result<()>;
124//!     pub async fn receive_many(&self, items: &mut [ReceiveItem<'_>]) -> io::Result<usize>;
125//!     pub fn capabilities(&self) -> &Capabilities;
126//!     pub fn local_addr(&self) -> io::Result<SocketAddr>;
127//! }
128//! ```
129//!
130//! The single-item methods are thin wrappers over the batch methods.
131//!
132//! # Design
133//!
134//! The crate uses tokio's `AsyncFd` for readiness notification and runs the
135//! batch and offload syscalls synchronously inside the `async_io` closure. This
136//! gives zero-copy, zero-allocation I/O: [`SendItem`] borrows the caller's
137//! `&[u8]` directly, with no channels, background tasks, or buffer copies.
138
139mod capability;
140mod ecn;
141mod item;
142mod socket;
143
144pub use capability::Capabilities;
145pub use ecn::Ecn;
146pub use item::{ReceiveItem, SendItem};
147pub use socket::{FastUdpSocket, FastUdpSocketBuilder};
148
149#[cfg(test)]
150mod tests;