Skip to main content

AsyncUdpSocket

Trait AsyncUdpSocket 

Source
pub trait AsyncUdpSocket:
    Send
    + Sync
    + Debug
    + 'static {
    // Required methods
    fn local_addr(&self) -> Result<SocketAddr>;
    fn poll_send(
        &self,
        cx: &mut Context<'_>,
        transmit: &Transmit<'_>,
    ) -> Poll<Result<usize>>;
    fn poll_recv(
        &self,
        cx: &mut Context<'_>,
        bufs: &mut [IoSliceMut<'_>],
        meta: &mut [RecvMeta],
    ) -> Poll<Result<usize>>;

    // Provided methods
    fn max_gso_segments(&self) -> usize { ... }
    fn max_gro_segments(&self) -> usize { ... }
    fn send_to<'a>(
        &'a self,
        buf: &'a [u8],
        target: SocketAddr,
    ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'a>> { ... }
    fn recv_from<'a>(
        &'a self,
        buf: &'a mut [u8],
    ) -> Pin<Box<dyn Future<Output = Result<(usize, SocketAddr)>> + Send + 'a>> { ... }
}
Expand description

Abstract implementation of a UDP socket for runtime independence.

§Poll-based, by design

The two packet primitives — poll_send and poll_recv — are synchronous and readiness-based, mirroring quinn’s AsyncUdpSocket. This is what keeps the packet path allocation-free: a boxed future per datagram would cost one heap allocation per send and per receive, and callers that merely want to test readiness (see the driver’s burst drain) would allocate just to discard.

An implementor supplies local_addr and the two poll primitives; everything else is defaulted. The two async methods (send_to, recv_from) are single-datagram conveniences for control-plane use, written over the primitives — they each box a future, so the packet path polls instead. Batching has no convenience wrappers by design: set Transmit::segment_size for poll_send, and on the receive side pass more than one buffer to poll_recv and read RecvMeta::stride from each message it fills.

Required Methods§

Source

fn local_addr(&self) -> Result<SocketAddr>

Get the local address this socket is bound to

Source

fn poll_send( &self, cx: &mut Context<'_>, transmit: &Transmit<'_>, ) -> Poll<Result<usize>>

Attempt to send transmit, registering cx’s waker if the socket is not writable yet.

contents goes to destination as a single datagram when segment_size is None. Some(n) requests UDP GSO: the buffer is split into consecutive n-byte datagrams (the last may be shorter) and emitted in one syscall. Callers should only supply contents spanning more than one segment when max_gso_segments reports > 1 — a socket reporting 1 may ignore segment_size and emit one oversized datagram.

ecn, when Some, stamps the ECN codepoint bits on every segment.

Returns the number of payload bytes accepted.

Source

fn poll_recv( &self, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta], ) -> Poll<Result<usize>>

Attempt to receive up to bufs.len() messages, registering cx’s waker if none is ready.

Fills bufs[..n] and meta[..n] and returns n, never more than bufs.len().min(meta.len()). Two independent kinds of batching are in play:

  • Multiple messages per syscall. Where the platform offers recvmmsg (Linux) or an equivalent, one call returns up to BATCH_SIZE datagrams from different peers. Elsewhere n is always 1, so a caller must never assume more.
  • GRO coalescing within a message. Each filled buffer may itself hold several consecutive same-flow datagrams — see RecvMeta::stride.

Implementations must report a stride of at least 1 for every filled message: quinn-udp mirrors len into stride, which is 0 for a zero-length datagram and would make de-segmentation divide by zero.

A minimal implementation may fill only bufs[0] and return Ok(1).

§Errors

Return the error rather than retrying inside the implementation: the driver classifies it and resumes the receive loop for anything transient. A socket serving many peers learns about per-peer failures through the socket itself, so ConnectionRefused (how an ICMP port-unreachable surfaces on Linux), ConnectionReset (the same on Windows), Interrupted, WouldBlock and TimedOut are all treated as transient and do not tear the socket down. Anything else is taken to mean the socket is unusable.

Provided Methods§

Source

fn max_gso_segments(&self) -> usize

Maximum number of segments a single poll_send call can emit in one syscall via UDP GSO. Returns 1 when GSO is unavailable (each segment then costs one syscall).

Callers must consult this before passing a segment_size that spans more than one datagram: a socket reporting 1 may ignore segment_size and emit one oversized datagram.

Source

fn max_gro_segments(&self) -> usize

Maximum number of datagrams the kernel may coalesce into a single message via UDP GRO — not into a whole poll_recv call, which may return up to BATCH_SIZE messages. Returns 1 when GRO is unavailable.

§Buffer sizing

This value decides how large a buffer the driver hands to poll_recv: reporting n > 1 asks for roughly n × 1500 bytes per message, because a coalesced segment is bounded by the path MTU, not by the largest datagram the application sends. Sizing against your own maximum datagram size instead is the easy mistake, and it fails quietly — the kernel drops the tail of a coalesced read, which looks like unexplained packet loss rather than an error.

Two consequences worth knowing before reporting > 1:

  • Buffers are allocated per socket per receive, so the count is an allocation multiplier. It is clamped internally, and 1500 is assumed per segment; paths with an MTU above 1500 are not supported for GRO and would truncate.
  • The exact formula is the driver’s own policy and may change. Report what the socket can actually coalesce and let the driver size accordingly — an implementation that allocates its own receive buffers to some other rule (a shared socket demultiplexed across connections, say) is responsible for the same MTU bound in its own loop.
Source

fn send_to<'a>( &'a self, buf: &'a [u8], target: SocketAddr, ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'a>>

Send buf as a single datagram to target.

Source

fn recv_from<'a>( &'a self, buf: &'a mut [u8], ) -> Pin<Box<dyn Future<Output = Result<(usize, SocketAddr)>> + Send + 'a>>

Receive a single datagram from the socket.

Convenience over poll_recv for control-plane callers wanting one datagram and no batching. It discards RecvMeta::stride, so do not use it where GRO coalescing is possible — the packet path polls instead.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§