quinn_udp/
lib.rs

1//! Uniform interface to send and receive UDP packets with advanced features useful for QUIC
2//!
3//! This crate exposes kernel UDP stack features available on most modern systems which are required
4//! for an efficient and conformant QUIC implementation. As of this writing, these are not available
5//! in std or major async runtimes, and their niche character and complexity are a barrier to adding
6//! them. Hence, a dedicated crate.
7//!
8//! Exposed features include:
9//!
10//! - Segmentation offload for bulk send and receive operations, reducing CPU load.
11//! - Reporting the exact destination address of received packets and specifying explicit source
12//!   addresses for sent packets, allowing responses to be sent from the address that the peer
13//!   expects when there are multiple possibilities. This is common when bound to a wildcard address
14//!   in IPv6 due to [RFC 8981] temporary addresses.
15//! - [Explicit Congestion Notification], which is required by QUIC to prevent packet loss and reduce
16//!   latency on congested links when supported by the network path.
17//! - Disabled IP-layer fragmentation, which allows the true physical MTU to be detected and reduces
18//!   risk of QUIC packet loss.
19//!
20//! Some features are unavailable in some environments. This can be due to an outdated operating
21//! system or drivers. Some operating systems may not implement desired features at all, or may not
22//! yet be supported by the crate. When support is unavailable, functionality will gracefully
23//! degrade.
24//!
25//! [RFC 8981]: https://www.rfc-editor.org/rfc/rfc8981.html
26//! [Explicit Congestion Notification]: https://www.rfc-editor.org/rfc/rfc3168.html
27#![warn(unreachable_pub)]
28#![warn(clippy::use_self)]
29
30use std::net::{IpAddr, Ipv6Addr, SocketAddr};
31#[cfg(unix)]
32use std::os::unix::io::AsFd;
33#[cfg(windows)]
34use std::os::windows::io::AsSocket;
35#[cfg(not(wasm_browser))]
36use std::{
37    sync::Mutex,
38    time::{Duration, Instant},
39};
40
41#[cfg(any(unix, windows))]
42mod cmsg;
43
44#[cfg(unix)]
45#[path = "unix.rs"]
46mod imp;
47
48#[cfg(windows)]
49#[path = "windows.rs"]
50mod imp;
51
52// No ECN support
53#[cfg(not(any(wasm_browser, unix, windows)))]
54#[path = "fallback.rs"]
55mod imp;
56
57#[allow(unused_imports, unused_macros)]
58mod log {
59    #[cfg(all(feature = "direct-log", not(feature = "tracing")))]
60    pub(crate) use log::{debug, error, info, trace, warn};
61
62    #[cfg(feature = "tracing")]
63    pub(crate) use tracing::{debug, error, info, trace, warn};
64
65    #[cfg(not(any(feature = "direct-log", feature = "tracing")))]
66    mod no_op {
67        macro_rules! trace    ( ($($tt:tt)*) => {{}} );
68        macro_rules! debug    ( ($($tt:tt)*) => {{}} );
69        macro_rules! info     ( ($($tt:tt)*) => {{}} );
70        macro_rules! log_warn ( ($($tt:tt)*) => {{}} );
71        macro_rules! error    ( ($($tt:tt)*) => {{}} );
72
73        pub(crate) use {debug, error, info, log_warn as warn, trace};
74    }
75
76    #[cfg(not(any(feature = "direct-log", feature = "tracing")))]
77    pub(crate) use no_op::*;
78}
79
80#[cfg(not(wasm_browser))]
81pub use imp::UdpSocketState;
82
83/// Number of UDP packets to send/receive at a time
84#[cfg(not(wasm_browser))]
85pub const BATCH_SIZE: usize = imp::BATCH_SIZE;
86/// Number of UDP packets to send/receive at a time
87#[cfg(wasm_browser)]
88pub const BATCH_SIZE: usize = 1;
89
90/// Metadata for a single buffer filled with bytes received from the network
91///
92/// This associated buffer can contain one or more datagrams, see [`stride`].
93///
94/// [`stride`]: RecvMeta::stride
95#[derive(Debug, Copy, Clone)]
96pub struct RecvMeta {
97    /// The source address of the datagram(s) contained in the buffer
98    pub addr: SocketAddr,
99    /// The number of bytes the associated buffer has
100    pub len: usize,
101    /// The size of a single datagram in the associated buffer
102    ///
103    /// When GRO (Generic Receive Offload) is used this indicates the size of a single
104    /// datagram inside the buffer. If the buffer is larger, that is if [`len`] is greater
105    /// then this value, then the individual datagrams contained have their boundaries at
106    /// `stride` increments from the start. The last datagram could be smaller than
107    /// `stride`.
108    ///
109    /// [`len`]: RecvMeta::len
110    pub stride: usize,
111    /// The Explicit Congestion Notification bits for the datagram(s) in the buffer
112    pub ecn: Option<EcnCodepoint>,
113    /// The destination IP address which was encoded in this datagram
114    ///
115    /// Populated on platforms: Windows, Linux, Android (API level > 25),
116    /// FreeBSD, OpenBSD, NetBSD, macOS, and iOS.
117    pub dst_ip: Option<IpAddr>,
118}
119
120impl Default for RecvMeta {
121    /// Constructs a value with arbitrary fields, intended to be overwritten
122    fn default() -> Self {
123        Self {
124            addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
125            len: 0,
126            stride: 0,
127            ecn: None,
128            dst_ip: None,
129        }
130    }
131}
132
133/// An outgoing packet
134#[derive(Debug, Clone)]
135pub struct Transmit<'a> {
136    /// The socket this datagram should be sent to
137    pub destination: SocketAddr,
138    /// Explicit congestion notification bits to set on the packet
139    pub ecn: Option<EcnCodepoint>,
140    /// Contents of the datagram
141    pub contents: &'a [u8],
142    /// The segment size if this transmission contains multiple datagrams.
143    /// This is `None` if the transmit only contains a single datagram
144    pub segment_size: Option<usize>,
145    /// Optional source IP address for the datagram
146    pub src_ip: Option<IpAddr>,
147}
148
149/// Log at most 1 IO error per minute
150#[cfg(not(wasm_browser))]
151const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60);
152
153/// Logs a warning message when sendmsg fails
154///
155/// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`]
156/// has elapsed since the last error was logged.
157#[cfg(all(not(wasm_browser), any(feature = "tracing", feature = "direct-log")))]
158fn log_sendmsg_error(
159    last_send_error: &Mutex<Instant>,
160    err: impl core::fmt::Debug,
161    transmit: &Transmit,
162) {
163    let now = Instant::now();
164    let last_send_error = &mut *last_send_error.lock().expect("poisend lock");
165    if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL {
166        *last_send_error = now;
167        log::warn!(
168        "sendmsg error: {:?}, Transmit: {{ destination: {:?}, src_ip: {:?}, ecn: {:?}, len: {:?}, segment_size: {:?} }}",
169            err, transmit.destination, transmit.src_ip, transmit.ecn, transmit.contents.len(), transmit.segment_size);
170    }
171}
172
173// No-op
174#[cfg(not(any(wasm_browser, feature = "tracing", feature = "direct-log")))]
175fn log_sendmsg_error(_: &Mutex<Instant>, _: impl core::fmt::Debug, _: &Transmit) {}
176
177/// A borrowed UDP socket
178///
179/// On Unix, constructible via `From<T: AsFd>`. On Windows, constructible via `From<T:
180/// AsSocket>`.
181// Wrapper around socket2 to avoid making it a public dependency and incurring stability risk
182#[cfg(not(wasm_browser))]
183pub struct UdpSockRef<'a>(socket2::SockRef<'a>);
184
185#[cfg(unix)]
186impl<'s, S> From<&'s S> for UdpSockRef<'s>
187where
188    S: AsFd,
189{
190    fn from(socket: &'s S) -> Self {
191        Self(socket.into())
192    }
193}
194
195#[cfg(windows)]
196impl<'s, S> From<&'s S> for UdpSockRef<'s>
197where
198    S: AsSocket,
199{
200    fn from(socket: &'s S) -> Self {
201        Self(socket.into())
202    }
203}
204
205/// Explicit congestion notification codepoint
206#[repr(u8)]
207#[derive(Debug, Copy, Clone, Eq, PartialEq)]
208pub enum EcnCodepoint {
209    /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable
210    Ect0 = 0b10,
211    /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable
212    Ect1 = 0b01,
213    /// The CE codepoint, signalling that congestion was experienced
214    Ce = 0b11,
215}
216
217impl EcnCodepoint {
218    /// Create new object from the given bits
219    pub fn from_bits(x: u8) -> Option<Self> {
220        use EcnCodepoint::*;
221        Some(match x & 0b11 {
222            0b10 => Ect0,
223            0b01 => Ect1,
224            0b11 => Ce,
225            _ => {
226                return None;
227            }
228        })
229    }
230}