//! Uniform interface to send and receive UDP packets with advanced features useful for QUIC
//!
//! This crate exposes kernel UDP stack features available on most modern systems which are required
//! for an efficient and conformant QUIC implementation. As of this writing, these are not available
//! in std or major async runtimes, and their niche character and complexity are a barrier to adding
//! them. Hence, a dedicated crate.
//!
//! Exposed features include:
//!
//! - Segmentation offload for bulk send and receive operations, reducing CPU load.
//! - Reporting the exact destination address of received packets and specifying explicit source
//!   addresses for sent packets, allowing responses to be sent from the address that the peer
//!   expects when there are multiple possibilities. This is common when bound to a wildcard address
//!   in IPv6 due to [RFC 8981] temporary addresses.
//! - [Explicit Congestion Notification], which is required by QUIC to prevent packet loss and reduce
//!   latency on congested links when supported by the network path.
//! - Disabled IP-layer fragmentation, which allows the true physical MTU to be detected and reduces
//!   risk of QUIC packet loss.
//!
//! Some features are unavailable in some environments. This can be due to an outdated operating
//! system or drivers. Some operating systems may not implement desired features at all, or may not
//! yet be supported by the crate. When support is unavailable, functionality will gracefully
//! degrade.
//!
//! [RFC 8981]: https://www.rfc-editor.org/rfc/rfc8981.html
//! [Explicit Congestion Notification]: https://www.rfc-editor.org/rfc/rfc3168.html
#![warn(unreachable_pub)]
#![warn(clippy::use_self)]

use std::net::{IpAddr, Ipv6Addr, SocketAddr};
#[cfg(unix)]
use std::os::unix::io::AsFd;
#[cfg(windows)]
use std::os::windows::io::AsSocket;
#[cfg(not(wasm_browser))]
use std::{
    sync::Mutex,
    time::{Duration, Instant},
};

#[cfg(any(unix, windows))]
mod cmsg;

#[cfg(unix)]
#[path = "unix.rs"]
mod imp;

#[cfg(windows)]
#[path = "windows.rs"]
mod imp;

// No ECN support
#[cfg(not(any(wasm_browser, unix, windows)))]
#[path = "fallback.rs"]
mod imp;

#[allow(unused_imports, unused_macros)]
mod log {
    #[cfg(all(feature = "log", not(feature = "tracing-log")))]
    pub(crate) use log::{debug, error, info, trace, warn};

    #[cfg(feature = "tracing-log")]
    pub(crate) use tracing::{debug, error, info, trace, warn};

    #[cfg(not(any(feature = "log", feature = "tracing-log")))]
    mod no_op {
        macro_rules! trace    ( ($($tt:tt)*) => {{}} );
        macro_rules! debug    ( ($($tt:tt)*) => {{}} );
        macro_rules! info     ( ($($tt:tt)*) => {{}} );
        macro_rules! log_warn ( ($($tt:tt)*) => {{}} );
        macro_rules! error    ( ($($tt:tt)*) => {{}} );

        pub(crate) use {debug, error, info, log_warn as warn, trace};
    }

    #[cfg(not(any(feature = "log", feature = "tracing-log")))]
    pub(crate) use no_op::*;
}

#[cfg(not(wasm_browser))]
pub use imp::UdpSocketState;

/// Number of UDP packets to send/receive at a time
#[cfg(not(wasm_browser))]
pub const BATCH_SIZE: usize = imp::BATCH_SIZE;
/// Number of UDP packets to send/receive at a time
#[cfg(wasm_browser)]
pub const BATCH_SIZE: usize = 1;

/// Metadata for a single buffer filled with bytes received from the network
///
/// This associated buffer can contain one or more datagrams, see [`stride`].
///
/// [`stride`]: RecvMeta::stride
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub struct RecvMeta {
    /// The source address of the datagram(s) contained in the buffer
    pub addr: SocketAddr,
    /// The number of bytes the associated buffer has
    pub len: usize,
    /// The size of a single datagram in the associated buffer
    ///
    /// When GRO (Generic Receive Offload) is used this indicates the size of a single
    /// datagram inside the buffer. If the buffer is larger, that is if [`len`] is greater
    /// then this value, then the individual datagrams contained have their boundaries at
    /// `stride` increments from the start. The last datagram could be smaller than
    /// `stride`.
    ///
    /// [`len`]: RecvMeta::len
    pub stride: usize,
    /// The Explicit Congestion Notification bits for the datagram(s) in the buffer
    pub ecn: Option<EcnCodepoint>,
    /// The destination IP address which was encoded in this datagram
    ///
    /// Populated on platforms: Windows (except under Wine), Linux, Android
    /// (API level > 25), FreeBSD, OpenBSD, NetBSD, macOS, and iOS.
    pub dst_ip: Option<IpAddr>,
    /// The interface index of the interface on which the datagram was received
    pub interface_index: Option<u32>,
}

impl Default for RecvMeta {
    /// Constructs a value with arbitrary fields, intended to be overwritten
    fn default() -> Self {
        Self {
            addr: SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
            len: 0,
            stride: 0,
            ecn: None,
            dst_ip: None,
            interface_index: None,
        }
    }
}

/// An outgoing packet
#[derive(Debug, Clone)]
pub struct Transmit<'a> {
    /// The socket this datagram should be sent to
    pub destination: SocketAddr,
    /// Explicit congestion notification bits to set on the packet
    pub ecn: Option<EcnCodepoint>,
    /// Contents of the datagram
    pub contents: &'a [u8],
    /// The segment size if this transmission contains multiple datagrams.
    /// This is `None` if the transmit only contains a single datagram
    pub segment_size: Option<usize>,
    /// Optional source IP address for the datagram
    pub src_ip: Option<IpAddr>,
}

impl Transmit<'_> {
    /// Computes the effective segment-size of the packet.
    ///
    /// Some (older) network drivers don't like being told to do GSO even if
    /// there is effectively only a single segment.
    /// (i.e. `segment_size == contents.len()`)
    /// Additionally, a `segment_size` that is greater than the content also
    /// means there is effectively only a single segment.
    /// This case is actually quite common when splitting up a prepared GSO batch
    /// again after GSO has been disabled because the last datagram in a GSO
    /// batch is allowed to be smaller than the segment size.
    fn effective_segment_size(&self) -> Option<usize> {
        match self.segment_size? {
            size if size >= self.contents.len() => None,
            size => Some(size),
        }
    }
}

/// Log at most 1 IO error per minute
#[cfg(not(wasm_browser))]
const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60);

/// Logs a warning message when sendmsg fails
///
/// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`]
/// has elapsed since the last error was logged.
#[cfg(all(not(wasm_browser), any(feature = "tracing-log", feature = "log")))]
fn log_sendmsg_error(
    last_send_error: &Mutex<Instant>,
    err: impl core::fmt::Debug,
    transmit: &Transmit<'_>,
) {
    let now = Instant::now();
    let last_send_error = &mut *last_send_error.lock().expect("poisend lock");
    if now.saturating_duration_since(*last_send_error) > IO_ERROR_LOG_INTERVAL {
        *last_send_error = now;
        log::warn!(
            "sendmsg error: {:?}, Transmit: {{ destination: {:?}, src_ip: {:?}, ecn: {:?}, len: {:?}, segment_size: {:?} }}",
            err,
            transmit.destination,
            transmit.src_ip,
            transmit.ecn,
            transmit.contents.len(),
            transmit.segment_size
        );
    }
}

// No-op
#[cfg(not(any(wasm_browser, feature = "tracing-log", feature = "log")))]
fn log_sendmsg_error(_: &Mutex<Instant>, _: impl core::fmt::Debug, _: &Transmit<'_>) {}

/// A borrowed UDP socket
///
/// On Unix, constructible via `From<T: AsFd>`. On Windows, constructible via `From<T:
/// AsSocket>`.
// Wrapper around socket2 to avoid making it a public dependency and incurring stability risk
#[cfg(not(wasm_browser))]
pub struct UdpSockRef<'a>(socket2::SockRef<'a>);

#[cfg(unix)]
impl<'s, S> From<&'s S> for UdpSockRef<'s>
where
    S: AsFd,
{
    fn from(socket: &'s S) -> Self {
        Self(socket.into())
    }
}

#[cfg(windows)]
impl<'s, S> From<&'s S> for UdpSockRef<'s>
where
    S: AsSocket,
{
    fn from(socket: &'s S) -> Self {
        Self(socket.into())
    }
}

/// Explicit congestion notification codepoint
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum EcnCodepoint {
    /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable
    Ect0 = 0b10,
    /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable
    Ect1 = 0b01,
    /// The CE codepoint, signalling that congestion was experienced
    Ce = 0b11,
}

impl EcnCodepoint {
    /// Create new object from the given bits
    pub fn from_bits(x: u8) -> Option<Self> {
        use EcnCodepoint::*;
        Some(match x & 0b11 {
            0b10 => Ect0,
            0b01 => Ect1,
            0b11 => Ce,
            _ => {
                return None;
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use std::net::Ipv4Addr;

    use super::*;

    #[test]
    fn effective_segment_size() {
        assert_eq!(
            make_transmit(&[0u8; 10], Some(15)).effective_segment_size(),
            None,
            "segment_size > content_len should yield no effective segment_size"
        );
        assert_eq!(
            make_transmit(&[0u8; 10], Some(10)).effective_segment_size(),
            None,
            "segment_size == content_len should yield no effective segment_size"
        );
        assert_eq!(
            make_transmit(&[0u8; 10], None).effective_segment_size(),
            None,
            "no segment_size should yield no effective segment_size"
        );
        assert_eq!(
            make_transmit(&[0u8; 10], Some(5)).effective_segment_size(),
            Some(5),
            "segment_size < content_len should yield effective segment_size"
        );
    }

    fn make_transmit(contents: &[u8], segment_size: Option<usize>) -> Transmit<'_> {
        Transmit {
            destination: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 1)),
            ecn: None,
            contents,
            segment_size,
            src_ip: None,
        }
    }
}

Homonyms

soft3/mir/src/lib.rs
soft3/strata/src/lib.rs
soft3/lens/src/lib.rs
soft3/crate/src/lib.rs
cyb/prysm/rs/lib.rs
cyb/honeycrisp/src/lib.rs
soft3/mudra/src/lib.rs
soft3/cybergraph/src/lib.rs
warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/nox/rs/lib.rs
warriors/trisha/rs/lib.rs
soft3/tru/rs/lib.rs
cyb/optica/src/lib.rs
warriors/trisha/honeycrisp/lib.rs
soft3/foculus/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
cyb/shell/src/lib.rs
neural/trident/src/lib.rs
neural/rune/rs/ast/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/dialect/src/lib.rs
neural/rune/rs/compile/lib.rs
neural/rs/link/src/lib.rs
neural/rune/rs/lex/lib.rs
soft3/hemera/rs/src/lib.rs
soft3/radio/iroh/src/lib.rs
soft3/lens/core/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/lens/binius/src/lib.rs
neural/rune/rs/interp/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rune/rs/mold/lib.rs
neural/rune/rs/subject/lib.rs
soft3/strata/proof/src/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/eidos/rs/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
soft3/lens/porphyry/src/lib.rs
neural/rs/macros/src/lib.rs
soft3/bbg/rs/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
neural/rs/mir-format/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/lens/assayer/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/strata/compute/src/lib.rs
soft3/tok/rs/src/lib.rs
soft3/strata/nebu/rs/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/conformance/rs/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
soft3/strata/ext/src/lib.rs
neural/rs/sigil/src/lib.rs
neural/rs/core/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/strata/core/src/lib.rs
neural/rs/codegen/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
neural/inf/rs/parse/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
neural/trident/editor/zed/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
neural/inf/rs/lex/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
soft3/radio/quinn/bench/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
neural/inf/rs/source/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
neural/inf/rs/lower/src/lib.rs
bootloader/go-cyber/cw/contracts/graph-filter/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/std-test/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std/src/lib.rs

Graph