Skip to main content

moq_uring/
udp.rs

1//! UDP through the worker's ring: batched receive, GSO send.
2//!
3//! Receive is one multishot `recvmsg` per socket feeding from a registered
4//! provided-buffer ring: each completion consumes one whole buffer, so every
5//! buffer is sized for the worst case (a full `UDP_GRO` coalesce plus the
6//! recvmsg header). Incremental consumption (`IOU_PBUF_RING_INC`) looked like
7//! a better fit for GRO's 100-byte-to-64KB completion variance, but it cannot
8//! back a multishot `recvmsg`: the kernel releases an incremental buffer only
9//! at exactly zero bytes left, and `io_recvmsg_prep_multishot` fails with
10//! `EFAULT` the moment a leftover tail is smaller than the recvmsg header.
11//! A completed buffer is handed out as a [`Packet`] and returns to the kernel
12//! once every packet borrowing it drops.
13//!
14//! Send stages datagrams in a pool of buffers owned by id and released
15//! explicitly on completion, the shape `SENDMSG_ZC`'s deferred-reclaim NOTIF
16//! model needs later. Every GSO `sendmsg` carries its `UDP_SEGMENT` control
17//! message explicitly; the socket default is never relied on. The ECN mark
18//! rides beside it as `IP_TOS` or `IPV6_TCLASS`, and receives read the mark
19//! back through `IP_RECVTOS` and `IPV6_RECVTCLASS`, so a QUIC stack's ECN
20//! validation sees what the network did to its packets.
21//!
22//! Both pools are queues of concurrent operations, not byte budgets: one send
23//! buffer holds one GSO train and one receive buffer holds one completion,
24//! however little either carries. So the depth a socket needs is set by how
25//! many of its connections want the socket at once, which no caller can
26//! predict. Both start small and grow on demand when they starve, bounded by
27//! the ceilings in [`Config`]; a pool never shrinks, so it settles at the
28//! socket's high-water concurrency and the memory comes back when it drops.
29//!
30//! The `gro`/`gso`/`multishot` toggles in [`Config`] exist for the ablation
31//! benchmarks; production callers keep the defaults (all on).
32
33use std::alloc::{Layout, alloc_zeroed, dealloc, handle_alloc_error};
34use std::cell::{Cell, RefCell};
35use std::collections::VecDeque;
36use std::io;
37use std::net::{IpAddr, SocketAddr, SocketAddrV6, UdpSocket};
38use std::os::fd::AsRawFd;
39use std::ptr::NonNull;
40use std::rc::Rc;
41use std::sync::atomic::{AtomicU16, Ordering};
42use std::task::Poll;
43
44use io_uring::{cqueue, opcode, types};
45
46use crate::Error;
47use crate::metrics::Counters;
48use crate::shared::{Cqe, Op, Shared};
49use crate::worker::Owner;
50
51/// Space reserved for received control messages: `UDP_GRO` plus the packet's
52/// `IP_TOS` or `IPV6_TCLASS` (the kernel emits one or the other), two ints.
53const CONTROL_LEN: usize = 64;
54/// Space reserved for the source address of each received datagram.
55const NAME_LEN: usize = std::mem::size_of::<libc::sockaddr_storage>();
56/// Fixed per-completion overhead of the multishot recvmsg layout.
57const RECV_OVERHEAD: usize = 16 + NAME_LEN + CONTROL_LEN;
58/// The largest payload one receive can produce: a full GRO coalesce.
59const MAX_RECV: usize = 64 * 1024;
60/// The kernel refuses GSO trains beyond this many segments.
61const MAX_GSO_SEGMENTS: usize = 64;
62/// The largest receive pool: the provided-buffer ring holds a power-of-two
63/// number of entries and the kernel caps it here.
64const MAX_RX_BUFFERS: u16 = 1 << 15;
65/// Receive buffers allocated before any starvation. Enough for an idle socket;
66/// the pool grows from here.
67const INITIAL_RX_BUFFERS: u16 = 16;
68/// Send buffers allocated before any starvation.
69const INITIAL_TX_BUFFERS: u16 = 64;
70
71/// Double a pool, bounded by its ceiling.
72fn grown(len: usize, max: u16) -> Option<u16> {
73	let max = usize::from(max);
74	match len < max {
75		true => Some(len.saturating_mul(2).clamp(1, max) as u16),
76		false => None,
77	}
78}
79
80/// How a socket uses the ring. The defaults are the production path; the
81/// toggles exist so the benchmarks can ablate one mechanism at a time.
82#[derive(Clone, Debug)]
83#[non_exhaustive]
84pub struct Config {
85	/// Coalesce received datagrams with `UDP_GRO`.
86	pub gro: bool,
87	/// Send with a `UDP_SEGMENT` control message instead of one `sendmsg` per
88	/// datagram.
89	pub gso: bool,
90	/// Receive through one persistent multishot `recvmsg` and the provided
91	/// buffer ring, instead of re-armed oneshot receives.
92	pub multishot: bool,
93	/// Receive pool ceiling: at most this many buffers, and at most 32768.
94	///
95	/// Each receive completion consumes one buffer whatever its size, so the
96	/// pool is a queue depth in packets rather than in bytes: GRO coalescing
97	/// collapses as connections multiply, and the depth a socket needs follows
98	/// that, not its bitrate. Buffers are allocated on demand, so this bounds
99	/// the memory rather than reserving it.
100	pub rx_buffers_max: u16,
101	/// Receive pool: bytes per buffer. Must hold one worst-case receive.
102	pub rx_buffer_len: usize,
103	/// Send pool ceiling: at most this many buffers, allocated on demand.
104	///
105	/// One buffer stages one GSO train, so the pool is the socket's in-flight
106	/// send concurrency. Set it to 1 to serialize sends.
107	pub tx_buffers_max: u16,
108	/// Send pool: bytes per buffer, the ceiling for one GSO train.
109	pub tx_buffer_len: usize,
110}
111
112impl Default for Config {
113	fn default() -> Self {
114		Self {
115			gro: true,
116			gso: true,
117			multishot: true,
118			// 16 MiB and 64 MiB of headroom at the default buffer lengths,
119			// reached only by a socket that actually starves for them.
120			rx_buffers_max: 256,
121			rx_buffer_len: MAX_RECV + RECV_OVERHEAD,
122			tx_buffers_max: 1024,
123			tx_buffer_len: 64 * 1024,
124		}
125	}
126}
127
128/// One buffer of the receive pool.
129struct RxBuf {
130	/// Stable heap allocation; [`Packet`]s hold raw slices into it.
131	data: Box<[u8]>,
132	/// Live [`Packet`]s borrowing slices of this buffer.
133	outstanding: usize,
134	/// Multishot: a completion consumed this buffer, so it recycles back into
135	/// the provided ring once `outstanding` drains.
136	kernel_done: bool,
137	/// Oneshot: an armed receive owns this buffer.
138	claimed: bool,
139}
140
141/// A received-but-not-consumed packet; materialized into a [`Packet`] on pop.
142struct Queued {
143	bid: u16,
144	start: usize,
145	len: usize,
146	from: SocketAddr,
147	stride: usize,
148	ecn: Option<Ecn>,
149}
150
151/// The ECN codepoint carried in the IP header's TOS or traffic class byte.
152#[repr(u8)]
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum Ecn {
155	/// ECN-capable transport, the codepoint classic ECN marks with.
156	Ect0 = 0b10,
157	/// ECN-capable transport, the codepoint L4S marks with.
158	Ect1 = 0b01,
159	/// Congestion experienced: a queue on the path marked the packet.
160	Ce = 0b11,
161}
162
163impl Ecn {
164	/// The codepoint in the low two bits of a TOS byte, if any.
165	fn from_bits(bits: u8) -> Option<Self> {
166		match bits & 0b11 {
167			0b10 => Some(Self::Ect0),
168			0b01 => Some(Self::Ect1),
169			0b11 => Some(Self::Ce),
170			_ => None,
171		}
172	}
173}
174
175/// What one [`TxBuf::send`] puts on the wire.
176#[derive(Debug, Clone, Copy)]
177pub struct Transmit {
178	/// The destination.
179	pub to: SocketAddr,
180	/// How many bytes of the buffer to send.
181	pub len: usize,
182	/// The datagram size; the buffer is sent as `len / segment` datagrams,
183	/// the last possibly short.
184	pub segment: usize,
185	/// The ECN codepoint every datagram carries, if any.
186	pub ecn: Option<Ecn>,
187}
188
189/// Free `bid` back to its pool if nothing borrows it any more. Returns whether
190/// the buffer became available for a new receive.
191fn recycle_if_idle(rx: &mut Rx, bid: u16) -> bool {
192	let buf = &mut rx.bufs[bid as usize];
193	if buf.outstanding > 0 {
194		return false;
195	}
196	if buf.claimed {
197		// Oneshot: the buffer frees wholesale.
198		buf.claimed = false;
199		return true;
200	}
201	if !buf.kernel_done {
202		// Multishot: the kernel still owns it.
203		return false;
204	}
205	// Multishot: hand the whole buffer back to the kernel.
206	buf.kernel_done = false;
207	let addr = buf.data.as_mut_ptr();
208	let len = buf.data.len();
209	if let Some(ring) = &mut rx.ring {
210		ring.add(bid, addr, len);
211		ring.publish();
212	}
213	true
214}
215
216/// Whether the receive pool has proven too shallow to arm against as it is.
217///
218/// A recorded starvation counts on its own: a buffer recycled between the
219/// kernel's `ENOBUFS` and this re-arm masks the shortfall without answering
220/// it, and arming into that one buffer just starves again.
221fn should_grow(rx: &Rx, multishot: bool) -> bool {
222	if rx.starved {
223		return true;
224	}
225	match multishot {
226		// Nothing left in the provided ring for the kernel to receive into.
227		true => !rx.bufs.iter().any(|buf| !buf.kernel_done),
228		// Every buffer is claimed by a receive or borrowed by a packet.
229		false => !rx.bufs.iter().any(|buf| !buf.claimed && buf.outstanding == 0),
230	}
231}
232
233/// Allocate more receive buffers and hand them straight to the kernel, up to
234/// [`Config::rx_buffers_max`]. Returns whether the pool grew.
235///
236/// Only the `RxBuf` structs move; [`Packet`] and the provided ring both point
237/// at the `Box<[u8]>` allocations, which stay put.
238fn grow_rx(rx: &mut Rx, config: &Config) -> bool {
239	let Some(target) = grown(rx.bufs.len(), config.rx_buffers_max) else {
240		return false;
241	};
242	while rx.bufs.len() < usize::from(target) {
243		let bid = rx.bufs.len() as u16;
244		rx.bufs.push(RxBuf {
245			data: vec![0u8; config.rx_buffer_len].into_boxed_slice(),
246			outstanding: 0,
247			kernel_done: false,
248			claimed: false,
249		});
250		if let Some(ring) = &mut rx.ring {
251			let buf = &mut rx.bufs[bid as usize];
252			let addr = buf.data.as_mut_ptr();
253			let len = buf.data.len();
254			ring.add(bid, addr, len);
255		}
256	}
257	if let Some(ring) = &mut rx.ring {
258		ring.publish();
259	}
260	true
261}
262
263/// Allocate more send buffers, up to [`Config::tx_buffers_max`]. Returns
264/// whether the pool grew.
265///
266/// The `Box<[u8]>` allocations are stable across the `Vec` growth, which is
267/// what lets a live [`TxBuf`] keep a raw pointer into one.
268fn grow_tx(tx: &mut Tx, config: &Config) -> bool {
269	let Some(target) = grown(tx.bufs.len(), config.tx_buffers_max) else {
270		return false;
271	};
272	while tx.bufs.len() < usize::from(target) {
273		tx.free.push(tx.bufs.len() as u16);
274		tx.bufs.push(TxSlot::new(config.tx_buffer_len));
275	}
276	true
277}
278
279/// The registered provided-buffer ring: kernel-shared memory we own.
280struct BufRing {
281	ptr: NonNull<types::BufRingEntry>,
282	layout: Layout,
283	mask: u16,
284	tail: u16,
285}
286
287impl BufRing {
288	fn new(entries: u16) -> Self {
289		let layout = Layout::from_size_align(entries as usize * std::mem::size_of::<types::BufRingEntry>(), 4096)
290			.expect("buffer ring layout");
291		// SAFETY: layout is non-zero.
292		let ptr = unsafe { alloc_zeroed(layout) };
293		let ptr = NonNull::new(ptr.cast::<types::BufRingEntry>()).unwrap_or_else(|| handle_alloc_error(layout));
294		Self {
295			ptr,
296			layout,
297			mask: entries - 1,
298			tail: 0,
299		}
300	}
301
302	/// Stage one buffer for the kernel; call [`publish`](Self::publish) after.
303	fn add(&mut self, bid: u16, addr: *mut u8, len: usize) {
304		let index = (self.tail & self.mask) as usize;
305		// SAFETY: index is masked into the allocation.
306		let entry = unsafe { &mut *self.ptr.as_ptr().add(index) };
307		entry.set_addr(addr as u64);
308		entry.set_len(len as u32);
309		entry.set_bid(bid);
310		self.tail = self.tail.wrapping_add(1);
311	}
312
313	/// Make staged buffers visible to the kernel.
314	fn publish(&self) {
315		// SAFETY: the tail pointer lives inside the registered allocation.
316		let tail = unsafe { types::BufRingEntry::tail(self.ptr.as_ptr()) }.cast_mut();
317		// SAFETY: the kernel reads this address atomically.
318		unsafe { AtomicU16::from_ptr(tail) }.store(self.tail, Ordering::Release);
319	}
320}
321
322impl Drop for BufRing {
323	fn drop(&mut self) {
324		// SAFETY: allocated in `new` with this layout; the caller unregisters
325		// the ring (or has torn down the io_uring) before dropping.
326		unsafe { dealloc(self.ptr.as_ptr().cast(), self.layout) };
327	}
328}
329
330/// Receive-side state.
331struct Rx {
332	bufs: Vec<RxBuf>,
333	ring: Option<BufRing>,
334	/// Multishot: the recvmsg header template (name + control sizes).
335	hdr: Box<libc::msghdr>,
336	queue: VecDeque<Queued>,
337	waiters: kio::WaiterList,
338	/// The slab key of the armed receive, if one is in flight.
339	armed: Option<u64>,
340	/// The kernel ran the pool dry since the last arm. Held rather than acted
341	/// on immediately because the re-arm is what can grow the pool.
342	starved: bool,
343	/// Terminal failure, surfaced by `poll_recv` once the queue drains.
344	error: Option<i32>,
345}
346
347/// Send-side state.
348struct Tx {
349	bufs: Vec<TxSlot>,
350	free: Vec<u16>,
351	waiters: kio::WaiterList,
352	/// Whether an acquisition is already waiting for the drained pool.
353	stalled: bool,
354	/// Terminal failure, surfaced by `poll_acquire`.
355	error: Option<i32>,
356}
357
358/// Stable storage reused by every checkout of one transmit slot: the payload
359/// the kernel reads and the `sendmsg` headers pointing into it.
360struct TxSlot {
361	data: Box<[u8]>,
362	headers: Vec<SendHdr>,
363	/// Sends staged from this slot that the kernel has not completed. The slot
364	/// returns to the free list when it hits zero.
365	in_flight: usize,
366}
367
368impl TxSlot {
369	fn new(len: usize) -> Self {
370		Self {
371			data: vec![0u8; len].into_boxed_slice(),
372			headers: Vec::new(),
373			in_flight: 0,
374		}
375	}
376}
377
378/// A bound socket a worker takes over, with whatever identity it carries.
379///
380/// Both variants convert with `From`, so [`crate::Handle::udp`] takes either
381/// as is. The member is the only way a socket gets a steering slot: the
382/// group that completed it is what proved the slot is real.
383pub enum Bound {
384	/// A socket on its own, bound by the caller.
385	Lone(UdpSocket),
386	/// One member of a completed steered `SO_REUSEPORT` group. Every
387	/// connection id an endpoint on it issues then leads with the member's
388	/// [`cid_prefix`](moq_sock::shard::cid_prefix), so the group's filter
389	/// keeps delivering a connection's packets to this socket.
390	Member(moq_sock::shard::Socket),
391}
392
393impl From<UdpSocket> for Bound {
394	fn from(socket: UdpSocket) -> Self {
395		Self::Lone(socket)
396	}
397}
398
399impl From<moq_sock::shard::Socket> for Bound {
400	fn from(member: moq_sock::shard::Socket) -> Self {
401		Self::Member(member)
402	}
403}
404
405/// Everything both the [`Socket`] handle and in-flight ops keep alive.
406pub(crate) struct SockShared {
407	io: UdpSocket,
408	/// The worker, as the I/O built on this socket carries it.
409	owner: Owner,
410	/// This socket's slot in a steered reuseport group, if it is in one.
411	shard: Option<moq_sock::shard::Shard>,
412	/// The worker's counters, held directly rather than reached through
413	/// `owner`, so counting a datagram is not a `Weak::upgrade`.
414	metrics: std::sync::Arc<Counters>,
415	config: Config,
416	bgid: u16,
417	closed: Cell<bool>,
418	rx: RefCell<Rx>,
419	tx: RefCell<Tx>,
420}
421
422impl SockShared {
423	/// Whether the worker loop that would drive this socket is gone: dropped
424	/// outright, or torn down while handles keep the shared state alive.
425	fn worker_gone(&self) -> bool {
426		self.owner.handle().is_none()
427	}
428
429	/// A packet released its buffer slice.
430	fn release_rx(self: &Rc<Self>, bid: u16) {
431		let mut rx = self.rx.borrow_mut();
432		rx.bufs[bid as usize].outstanding -= 1;
433		if !recycle_if_idle(&mut rx, bid) {
434			return;
435		}
436		// A receive that died on ENOBUFS can start again now.
437		if rx.armed.is_none() && rx.error.is_none() {
438			drop(rx);
439			if let Some(shared) = self.owner.upgrade() {
440				arm_recv(&shared, self);
441			}
442		}
443	}
444
445	fn release_tx(&self, id: u16) {
446		let mut tx = self.tx.borrow_mut();
447		debug_assert_eq!(tx.bufs[id as usize].in_flight, 0);
448		tx.free.push(id);
449		tx.stalled = false;
450		tx.waiters.wake();
451	}
452
453	fn stage_tx(&self, id: u16) {
454		self.tx.borrow_mut().bufs[id as usize].in_flight += 1;
455	}
456
457	fn complete_tx(&self, id: u16) {
458		let mut tx = self.tx.borrow_mut();
459		let slot = &mut tx.bufs[id as usize];
460		debug_assert!(slot.in_flight > 0);
461		slot.in_flight -= 1;
462		if slot.in_flight == 0 {
463			tx.free.push(id);
464			tx.stalled = false;
465			tx.waiters.wake();
466		}
467	}
468
469	fn fail_rx(&self, code: i32) {
470		let mut rx = self.rx.borrow_mut();
471		rx.error.get_or_insert(code);
472		rx.waiters.wake();
473	}
474
475	fn fail_tx(&self, code: i32) {
476		let mut tx = self.tx.borrow_mut();
477		tx.error.get_or_insert(code);
478		tx.waiters.wake();
479	}
480}
481
482impl Drop for SockShared {
483	fn drop(&mut self) {
484		// Every op referencing our buffers has completed (ops own an `Rc` of
485		// us), so the kernel is done; give the buffer group id back.
486		if self.rx.borrow().ring.is_some()
487			&& let Some(shared) = self.owner.upgrade()
488		{
489			let ring = shared.ring.borrow_mut();
490			let _ = ring.submitter().unregister_buf_ring(self.bgid);
491		}
492	}
493}
494
495/// A UDP socket driven by a [`crate::Worker`].
496///
497/// Created by [`crate::Handle::udp`]. Dropping it cancels the armed receive
498/// and releases the socket once the kernel confirms.
499pub struct Socket {
500	shared: Rc<SockShared>,
501}
502
503impl Socket {
504	/// A test-only observer for whether every kernel operation released this socket.
505	#[cfg(test)]
506	pub(crate) fn downgrade(&self) -> std::rc::Weak<SockShared> {
507		Rc::downgrade(&self.shared)
508	}
509
510	pub(crate) fn bind(shared: &Rc<Shared>, bound: Bound, config: Config) -> Result<Self, Error> {
511		let (io, shard) = match bound {
512			Bound::Lone(io) => (io, None),
513			Bound::Member(member) => {
514				let shard = member.shard();
515				(member.into_inner(), Some(shard))
516			}
517		};
518		let floor = if config.gro { MAX_RECV + RECV_OVERHEAD } else { 2048 };
519		if config.rx_buffer_len < floor || config.rx_buffers_max == 0 || config.tx_buffers_max == 0 {
520			return Err(io::Error::new(
521				io::ErrorKind::InvalidInput,
522				format!(
523					"receive buffers must hold one worst-case receive ({floor} bytes) and both pools need at least one buffer"
524				),
525			)
526			.into());
527		}
528		// Rounding up past `u16::MAX` would wrap to a zero-entry ring.
529		if config.rx_buffers_max > MAX_RX_BUFFERS {
530			return Err(io::Error::new(
531				io::ErrorKind::InvalidInput,
532				format!(
533					"receive pool holds at most {MAX_RX_BUFFERS} buffers, got {}",
534					config.rx_buffers_max
535				),
536			)
537			.into());
538		}
539
540		if config.gro {
541			set_option(&io, libc::SOL_UDP, libc::UDP_GRO)?;
542		}
543		// Receive the ECN codepoint with each datagram. A v6 socket takes
544		// both: Linux reports a v4-mapped datagram's mark as `IP_TOS`.
545		if io.local_addr()?.is_ipv6() {
546			set_option(&io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS)?;
547		}
548		set_option(&io, libc::IPPROTO_IP, libc::IP_RECVTOS)?;
549
550		// The ring's entry count is fixed at registration, so it is sized for
551		// the ceiling; the buffers behind it are allocated as the pool grows.
552		let rx_cap = config.rx_buffers_max.next_power_of_two();
553		let rx_count = INITIAL_RX_BUFFERS.min(config.rx_buffers_max);
554		let mut bufs = Vec::with_capacity(rx_count as usize);
555		for _ in 0..rx_count {
556			bufs.push(RxBuf {
557				data: vec![0u8; config.rx_buffer_len].into_boxed_slice(),
558				outstanding: 0,
559				kernel_done: false,
560				claimed: false,
561			});
562		}
563
564		let bgid = shared.next_bgid.get();
565		shared
566			.next_bgid
567			.set(bgid.checked_add(1).expect("buffer group ids exhausted"));
568
569		let ring = if config.multishot {
570			let mut ring = BufRing::new(rx_cap);
571			{
572				let io_ring = shared.ring.borrow_mut();
573				// SAFETY: the ring allocation lives in `SockShared`, which the
574				// armed receive's `Op` keeps alive until its terminal CQE, and
575				// is unregistered before it drops.
576				unsafe {
577					io_ring
578						.submitter()
579						.register_buf_ring_with_flags(ring.ptr.as_ptr() as u64, rx_cap, bgid, 0)
580						.map_err(Error::ring)?;
581				}
582			}
583			for (bid, buf) in bufs.iter_mut().enumerate() {
584				let addr = buf.data.as_mut_ptr();
585				let len = buf.data.len();
586				ring.add(bid as u16, addr, len);
587			}
588			ring.publish();
589			Some(ring)
590		} else {
591			None
592		};
593
594		let mut hdr: Box<libc::msghdr> = Box::new(unsafe { std::mem::zeroed() });
595		hdr.msg_namelen = NAME_LEN as libc::socklen_t;
596		hdr.msg_controllen = CONTROL_LEN;
597
598		let tx_count = INITIAL_TX_BUFFERS.min(config.tx_buffers_max);
599		let tx = Tx {
600			bufs: (0..tx_count).map(|_| TxSlot::new(config.tx_buffer_len)).collect(),
601			free: (0..tx_count).collect(),
602			waiters: kio::WaiterList::new(),
603			stalled: false,
604			error: None,
605		};
606
607		let sock = Rc::new(SockShared {
608			io,
609			owner: Owner::new(shared),
610			shard,
611			metrics: shared.metrics.clone(),
612			config,
613			bgid,
614			closed: Cell::new(false),
615			rx: RefCell::new(Rx {
616				bufs,
617				ring,
618				hdr,
619				queue: VecDeque::new(),
620				waiters: kio::WaiterList::new(),
621				armed: None,
622				starved: false,
623				error: None,
624			}),
625			tx: RefCell::new(tx),
626		});
627
628		arm_recv(shared, &sock);
629		if let Some(code) = sock.rx.borrow().error {
630			return Err(io::Error::from_raw_os_error(code).into());
631		}
632		Ok(Self { shared: sock })
633	}
634
635	/// The bound local address.
636	pub fn local_addr(&self) -> io::Result<SocketAddr> {
637		self.shared.io.local_addr()
638	}
639
640	/// The worker driving this socket, which everything built on it runs on.
641	pub(crate) fn owner(&self) -> Owner {
642		self.shared.owner.clone()
643	}
644
645	/// This socket's slot in a steered reuseport group, if it is in one.
646	pub(crate) fn shard(&self) -> Option<moq_sock::shard::Shard> {
647		self.shared.shard
648	}
649
650	/// A received packet, or the socket's terminal error, registering `waiter`
651	/// while neither is available. Queued packets drain before an error
652	/// surfaces.
653	pub fn poll_recv(&self, waiter: &kio::Waiter) -> Poll<io::Result<Packet>> {
654		let mut rx = self.shared.rx.borrow_mut();
655		if let Some(queued) = rx.queue.pop_front() {
656			let buf = &rx.bufs[queued.bid as usize];
657			// SAFETY: `start..start + len` is in bounds; the allocation is
658			// stable and the range is exclusively this packet's (see Packet).
659			let ptr = unsafe { NonNull::new_unchecked(buf.data.as_ptr().cast_mut().add(queued.start)) };
660			return Poll::Ready(Ok(Packet {
661				sock: self.shared.clone(),
662				bid: queued.bid,
663				ptr,
664				len: queued.len,
665				stride: queued.stride,
666				from: queued.from,
667				ecn: queued.ecn,
668			}));
669		}
670		if let Some(code) = rx.error {
671			return Poll::Ready(Err(io::Error::from_raw_os_error(code)));
672		}
673		if self.shared.worker_gone() {
674			return Poll::Ready(Err(Shared::gone_error()));
675		}
676		waiter.register(&mut rx.waiters);
677		Poll::Pending
678	}
679
680	/// Await [`poll_recv`](Self::poll_recv).
681	pub async fn recv(&self) -> io::Result<Packet> {
682		kio::wait(|waiter| self.poll_recv(waiter)).await
683	}
684
685	/// A free send-staging buffer, registering `waiter` while the pool is
686	/// drained. Backpressure lives here: the pool caps in-flight sends.
687	pub fn poll_acquire(&self, waiter: &kio::Waiter) -> Poll<io::Result<TxBuf>> {
688		let mut tx = self.shared.tx.borrow_mut();
689		if let Some(code) = tx.error {
690			return Poll::Ready(Err(io::Error::from_raw_os_error(code)));
691		}
692		if self.shared.worker_gone() {
693			return Poll::Ready(Err(Shared::gone_error()));
694		}
695		if tx.free.is_empty() {
696			// Starved: every buffer is in flight, so the socket needs a deeper
697			// send window than it has. Grow rather than serialize behind it.
698			grow_tx(&mut tx, &self.shared.config);
699		}
700		if let Some(id) = tx.free.pop() {
701			let slot = &mut tx.bufs[id as usize];
702			// SAFETY: `id` was exclusively checked out of the free list; the
703			// allocation is stable (see TxBuf).
704			let ptr = unsafe { NonNull::new_unchecked(slot.data.as_mut_ptr()) };
705			let cap = slot.data.len();
706			return Poll::Ready(Ok(TxBuf {
707				sock: self.shared.clone(),
708				id,
709				ptr,
710				cap,
711				armed: false,
712			}));
713		}
714		if !tx.stalled {
715			tx.stalled = true;
716			self.shared.metrics.tx_stalls.add(1);
717		}
718		waiter.register(&mut tx.waiters);
719		Poll::Pending
720	}
721
722	/// Await [`poll_acquire`](Self::poll_acquire).
723	pub async fn acquire(&self) -> io::Result<TxBuf> {
724		kio::wait(|waiter| self.poll_acquire(waiter)).await
725	}
726}
727
728impl Drop for Socket {
729	fn drop(&mut self) {
730		self.shared.closed.set(true);
731		let rx = self.shared.rx.borrow();
732		if let (Some(key), Some(shared)) = (rx.armed, self.shared.owner.upgrade()) {
733			drop(rx);
734			// Fire-and-forget: the cancel's own CQE is consumed by the worker,
735			// and the receive's terminal CQE releases the socket state.
736			let _ = shared.cancel(key);
737		}
738	}
739}
740
741impl std::fmt::Debug for Socket {
742	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743		f.debug_struct("Socket")
744			.field("addr", &self.shared.io.local_addr())
745			.finish()
746	}
747}
748
749/// One receive: a possibly GRO-coalesced run of datagrams from one source.
750///
751/// Borrows its worker's receive pool; drop it to hand the space back. The
752/// payload is `stride`-sized datagrams, the last possibly short.
753pub struct Packet {
754	sock: Rc<SockShared>,
755	bid: u16,
756	ptr: NonNull<u8>,
757	len: usize,
758	stride: usize,
759	from: SocketAddr,
760	ecn: Option<Ecn>,
761}
762
763impl Packet {
764	/// The datagrams' source address.
765	pub fn from(&self) -> SocketAddr {
766		self.from
767	}
768
769	/// The ECN codepoint the datagrams arrived with; GRO only coalesces
770	/// datagrams that share one.
771	pub fn ecn(&self) -> Option<Ecn> {
772		self.ecn
773	}
774
775	/// The datagram size GRO coalesced with; the final datagram may be short.
776	pub fn stride(&self) -> usize {
777		self.stride
778	}
779
780	/// The whole coalesced payload.
781	pub fn payload(&self) -> &[u8] {
782		// SAFETY: exclusive, in-bounds range of a stable allocation that the
783		// `sock` Rc keeps alive; the pool never touches it while outstanding.
784		unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
785	}
786
787	/// The whole coalesced payload, mutably (QUIC decrypts in place).
788	pub fn payload_mut(&mut self) -> &mut [u8] {
789		// SAFETY: as `payload`, and `&mut self` forbids aliasing our slices.
790		unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
791	}
792
793	/// The individual datagrams.
794	pub fn segments(&mut self) -> impl Iterator<Item = &mut [u8]> {
795		let stride = self.stride;
796		self.payload_mut().chunks_mut(stride)
797	}
798}
799
800impl Drop for Packet {
801	fn drop(&mut self) {
802		self.sock.release_rx(self.bid);
803	}
804}
805
806impl std::fmt::Debug for Packet {
807	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
808		f.debug_struct("Packet")
809			.field("from", &self.from)
810			.field("len", &self.len)
811			.field("stride", &self.stride)
812			.field("ecn", &self.ecn)
813			.finish()
814	}
815}
816
817/// A checked-out send-staging buffer: fill it, then [`send`](Self::send) it.
818///
819/// The buffer belongs to the socket it was acquired from, and sending goes
820/// back through that socket. Dropping it unsent returns it to the pool.
821pub struct TxBuf {
822	sock: Rc<SockShared>,
823	id: u16,
824	ptr: NonNull<u8>,
825	cap: usize,
826	armed: bool,
827}
828
829impl TxBuf {
830	/// Send `self[..len]` on the owning socket as datagrams of `segment`
831	/// bytes (the last may be short), marked with `ecn`. Fire-and-forget:
832	/// the buffer returns to the pool when the kernel completes, and a
833	/// failed send surfaces on the next pool acquire.
834	///
835	/// This only stages an SQE, so the datagram reaches the kernel when the
836	/// worker next enters the ring. Dropping the worker makes a bounded attempt
837	/// to submit staged datagrams and drain their completions, but does not
838	/// guarantee kernel completion or delivery.
839	pub fn send(mut self, transmit: Transmit) -> io::Result<()> {
840		let Transmit { to, len, segment, ecn } = transmit;
841		// `UDP_SEGMENT` is a u16, so an oversized segment would silently
842		// truncate into a tiny stride and explode the implied segment count.
843		if len == 0 || len > self.cap || segment == 0 || segment > usize::from(u16::MAX) {
844			return Err(io::Error::new(
845				io::ErrorKind::InvalidInput,
846				format!(
847					"invalid send: {len} bytes in {segment} byte segments from a {} byte buffer",
848					self.cap
849				),
850			));
851		}
852		let shared = match self.sock.owner.upgrade() {
853			Some(shared) if !shared.stopped.get() => shared,
854			_ => return Err(Shared::gone_error()),
855		};
856
857		// A GSO train is one `sendmsg` the kernel caps at 64 segments. Without
858		// GSO every segment is its own `sendmsg`, so the ring is the limit
859		// instead: staging more than the submission queue holds makes `push`
860		// submit inline and go round again without reaping a single
861		// completion, which starves the worker and overflows the queue.
862		let segments = len.div_ceil(segment);
863		let limit = match self.sock.config.gso {
864			true => MAX_GSO_SEGMENTS,
865			false => shared.ring.borrow().params().sq_entries() as usize,
866		};
867		if segments > limit {
868			return Err(io::Error::new(
869				io::ErrorKind::InvalidInput,
870				format!("send of {segments} datagrams exceeds the {limit} one call may stage"),
871			));
872		}
873
874		self.armed = true;
875		let sock = self.sock.clone();
876		let base = self.ptr.as_ptr();
877		let headers = {
878			let mut tx = sock.tx.borrow_mut();
879			let headers = &mut tx.bufs[self.id as usize].headers;
880			if headers.len() < segments {
881				headers.resize_with(segments, SendHdr::zeroed);
882			}
883			// SAFETY: the slot is checked out, so it cannot be sent from again
884			// (and its headers cannot grow again) until every send below
885			// completes and returns it to the free list.
886			unsafe { NonNull::new_unchecked(headers.as_mut_ptr()) }
887		};
888		let staging = Staging {
889			sock: sock.clone(),
890			id: self.id,
891			headers,
892		};
893
894		let one = SendOne {
895			to,
896			ecn,
897			segment: sock.config.gso.then_some(segment as u16),
898		};
899		if sock.config.gso {
900			send_one(&shared, &staging, 0, base, len, &one)?;
901		} else {
902			for index in 0..segments {
903				let offset = index * segment;
904				let chunk = segment.min(len - offset);
905				// SAFETY: offset stays within the leased buffer.
906				send_one(&shared, &staging, index, unsafe { base.add(offset) }, chunk, &one)?;
907			}
908		}
909		sock.metrics.tx_datagrams.add(segments as u64);
910		Ok(())
911	}
912}
913
914impl std::ops::Deref for TxBuf {
915	type Target = [u8];
916
917	fn deref(&self) -> &[u8] {
918		// SAFETY: `id` is exclusively ours until release; stable allocation.
919		unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.cap) }
920	}
921}
922
923impl std::ops::DerefMut for TxBuf {
924	fn deref_mut(&mut self) -> &mut [u8] {
925		// SAFETY: as `deref`.
926		unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.cap) }
927	}
928}
929
930impl Drop for TxBuf {
931	fn drop(&mut self) {
932		if !self.armed {
933			self.sock.release_tx(self.id);
934		}
935	}
936}
937
938impl std::fmt::Debug for TxBuf {
939	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940		f.debug_struct("TxBuf").field("cap", &self.cap).finish()
941	}
942}
943
944/// Control-message space, aligned like `cmsghdr` demands.
945#[repr(C, align(8))]
946struct Control([u8; CONTROL_LEN]);
947
948/// The stable storage one in-flight `sendmsg` points the kernel at.
949struct SendHdr {
950	hdr: libc::msghdr,
951	iov: libc::iovec,
952	name: libc::sockaddr_storage,
953	control: Control,
954}
955
956impl SendHdr {
957	fn zeroed() -> Self {
958		// SAFETY: all-zero is valid for these C structs.
959		unsafe { std::mem::zeroed() }
960	}
961}
962
963/// What every send from one [`TxBuf::send`] call stages against.
964struct Staging {
965	sock: Rc<SockShared>,
966	id: u16,
967	/// The slot's headers, one per datagram this call stages.
968	headers: NonNull<SendHdr>,
969}
970
971/// One in-flight `sendmsg`. The socket owns the header and payload it points
972/// the kernel at; dropping this releases the claim on that transmit slot.
973pub(crate) struct SendOp {
974	sock: Rc<SockShared>,
975	id: u16,
976	expect: usize,
977}
978
979impl Drop for SendOp {
980	fn drop(&mut self) {
981		self.sock.complete_tx(self.id);
982	}
983}
984
985/// What every datagram of one [`TxBuf::send`] shares.
986struct SendOne {
987	to: SocketAddr,
988	ecn: Option<Ecn>,
989	/// The `UDP_SEGMENT` size, when the call is one GSO train.
990	segment: Option<u16>,
991}
992
993fn send_one(
994	shared: &Rc<Shared>,
995	staging: &Staging,
996	index: usize,
997	base: *mut u8,
998	len: usize,
999	one: &SendOne,
1000) -> io::Result<()> {
1001	let SendOne { to, ecn, segment } = *one;
1002	// SAFETY: `index` is within the headers `TxBuf::send` reserved, and every
1003	// operation gets its own.
1004	let hdr = unsafe { &mut *staging.headers.as_ptr().add(index) };
1005	*hdr = SendHdr::zeroed();
1006	hdr.iov = libc::iovec {
1007		iov_base: base.cast(),
1008		iov_len: len,
1009	};
1010	let name_len = encode_addr(to, &mut hdr.name);
1011	hdr.hdr.msg_name = (&raw mut hdr.name).cast();
1012	hdr.hdr.msg_namelen = name_len;
1013	hdr.hdr.msg_iov = &raw mut hdr.iov;
1014	hdr.hdr.msg_iovlen = 1;
1015
1016	// SAFETY: the control buffer is zeroed, aligned, and holds both messages
1017	// (`CMSG_SPACE` of a u16 and of an int fit twice over in `CONTROL_LEN`);
1018	// `msg_controllen` is set to the total first so `CMSG_NXTHDR` walks it.
1019	unsafe {
1020		let mut space = 0;
1021		if segment.is_some() {
1022			space += libc::CMSG_SPACE(std::mem::size_of::<u16>() as _) as usize;
1023		}
1024		if ecn.is_some() {
1025			space += libc::CMSG_SPACE(std::mem::size_of::<libc::c_int>() as _) as usize;
1026		}
1027		if space > 0 {
1028			hdr.hdr.msg_control = hdr.control.0.as_mut_ptr().cast();
1029			hdr.hdr.msg_controllen = space;
1030		}
1031		let mut cmsg = libc::CMSG_FIRSTHDR(&hdr.hdr);
1032		if let Some(segment) = segment {
1033			(*cmsg).cmsg_level = libc::SOL_UDP;
1034			(*cmsg).cmsg_type = libc::UDP_SEGMENT;
1035			(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<u16>() as _) as usize;
1036			std::ptr::write_unaligned(libc::CMSG_DATA(cmsg).cast::<u16>(), segment);
1037			cmsg = libc::CMSG_NXTHDR(&hdr.hdr, cmsg);
1038		}
1039		if let Some(ecn) = ecn {
1040			// A v4-mapped destination on a v6 socket leaves as IPv4, so the
1041			// mark rides `IP_TOS`; a native v6 destination takes `IPV6_TCLASS`.
1042			let is_ipv4 = match to.ip() {
1043				IpAddr::V4(_) => true,
1044				IpAddr::V6(v6) => v6.to_ipv4_mapped().is_some(),
1045			};
1046			let (level, kind) = match is_ipv4 {
1047				true => (libc::IPPROTO_IP, libc::IP_TOS),
1048				false => (libc::IPPROTO_IPV6, libc::IPV6_TCLASS),
1049			};
1050			(*cmsg).cmsg_level = level;
1051			(*cmsg).cmsg_type = kind;
1052			(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<libc::c_int>() as _) as usize;
1053			std::ptr::write_unaligned(libc::CMSG_DATA(cmsg).cast::<libc::c_int>(), ecn as libc::c_int);
1054		}
1055	}
1056	let hdr_ptr = &raw const hdr.hdr;
1057
1058	// Count the send before the slab owns it, so the `SendOp` below is the only
1059	// thing that can release the slot. Completions only run from the worker's
1060	// pump, so the count cannot reach zero while this call is still staging.
1061	staging.sock.stage_tx(staging.id);
1062	let key = shared.insert(Op::Send(SendOp {
1063		sock: staging.sock.clone(),
1064		id: staging.id,
1065		expect: len,
1066	}));
1067	let entry = opcode::SendMsg::new(types::Fd(staging.sock.io.as_raw_fd()), hdr_ptr)
1068		.build()
1069		.user_data(key);
1070	if let Err(err) = shared.push(&entry) {
1071		shared.ops.borrow_mut().remove(key as usize);
1072		return Err(err);
1073	}
1074	staging.sock.metrics.tx_sends.add(1);
1075	Ok(())
1076}
1077
1078/// Arm (or re-arm) the socket's receive. Failure is recorded on the socket.
1079pub(crate) fn arm_recv(shared: &Rc<Shared>, sock: &Rc<SockShared>) {
1080	if sock.closed.get() || shared.stopped.get() {
1081		return;
1082	}
1083	let mut rx = sock.rx.borrow_mut();
1084	if rx.armed.is_some() || rx.error.is_some() {
1085		return;
1086	}
1087
1088	// Grow before arming when the pool has proven too shallow, so the next
1089	// receive has somewhere to land instead of waiting on a live packet to be
1090	// released and dropping every datagram until then.
1091	if should_grow(&rx, sock.config.multishot) {
1092		rx.starved = false;
1093		grow_rx(&mut rx, &sock.config);
1094	}
1095
1096	let entry = if sock.config.multishot {
1097		// Only arm with buffers in the provided ring (`!kernel_done`), or the
1098		// receive would die on ENOBUFS immediately and re-arming here would
1099		// spin.
1100		if !rx.bufs.iter().any(|buf| !buf.kernel_done) {
1101			sock.metrics.rx_exhausted.add(1);
1102			return;
1103		}
1104		let key = shared.insert(Op::Recv {
1105			sock: sock.clone(),
1106			one: None,
1107		});
1108		rx.armed = Some(key);
1109		opcode::RecvMsgMulti::new(types::Fd(sock.io.as_raw_fd()), &*rx.hdr, sock.bgid)
1110			.build()
1111			.user_data(key)
1112	} else {
1113		// Claim a whole free buffer for this one receive.
1114		let Some(bid) = rx
1115			.bufs
1116			.iter()
1117			.position(|buf| !buf.claimed && buf.outstanding == 0)
1118			.map(|bid| bid as u16)
1119		else {
1120			// Every buffer is borrowed and the pool is at its ceiling; a
1121			// release re-arms us.
1122			sock.metrics.rx_exhausted.add(1);
1123			return;
1124		};
1125		rx.bufs[bid as usize].claimed = true;
1126
1127		// SAFETY: all-zero is valid for these C structs.
1128		let mut one: Box<OneshotRecv> = Box::new(unsafe { std::mem::zeroed() });
1129		one.bid = bid;
1130		one.iov = libc::iovec {
1131			iov_base: rx.bufs[bid as usize].data.as_mut_ptr().cast(),
1132			iov_len: rx.bufs[bid as usize].data.len(),
1133		};
1134		one.hdr.msg_name = (&raw mut one.name).cast();
1135		one.hdr.msg_namelen = NAME_LEN as libc::socklen_t;
1136		one.hdr.msg_iov = &raw mut one.iov;
1137		one.hdr.msg_iovlen = 1;
1138		one.hdr.msg_control = one.control.0.as_mut_ptr().cast();
1139		one.hdr.msg_controllen = CONTROL_LEN;
1140
1141		let hdr_ptr = &raw mut one.hdr;
1142		let key = shared.insert(Op::Recv {
1143			sock: sock.clone(),
1144			one: Some(one),
1145		});
1146		rx.armed = Some(key);
1147		opcode::RecvMsg::new(types::Fd(sock.io.as_raw_fd()), hdr_ptr)
1148			.build()
1149			.user_data(key)
1150	};
1151
1152	drop(rx);
1153	if let Err(err) = shared.push(&entry) {
1154		let key = sock.rx.borrow_mut().armed.take().expect("just armed");
1155		shared.ops.borrow_mut().remove(key as usize);
1156		sock.fail_rx(err.raw_os_error().unwrap_or(libc::EIO));
1157	}
1158}
1159
1160/// The oneshot receive's stable kernel-visible storage and buffer claim.
1161pub(crate) struct OneshotRecv {
1162	hdr: libc::msghdr,
1163	iov: libc::iovec,
1164	name: libc::sockaddr_storage,
1165	control: Control,
1166	bid: u16,
1167}
1168
1169/// Handle one receive completion. `terminal` means the op left the slab (the
1170/// multishot ended or this was a oneshot), so a re-arm may be needed.
1171pub(crate) fn on_recv(
1172	shared: &Rc<Shared>,
1173	sock: &Rc<SockShared>,
1174	one: Option<Box<OneshotRecv>>,
1175	cqe: Cqe,
1176	terminal: bool,
1177) {
1178	if terminal {
1179		sock.rx.borrow_mut().armed = None;
1180	}
1181
1182	if cqe.result < 0 {
1183		let code = -cqe.result;
1184		if let Some(one) = &one {
1185			let mut rx = sock.rx.borrow_mut();
1186			rx.bufs[one.bid as usize].claimed = false;
1187		}
1188		match code {
1189			// The receive pool is exhausted. Record it: by the time the re-arm
1190			// looks, a recycled buffer may hide that the kernel ran dry.
1191			libc::ENOBUFS => {
1192				sock.metrics.rx_enobufs.add(1);
1193				sock.rx.borrow_mut().starved = true;
1194			}
1195			// Socket teardown; nothing to surface.
1196			libc::ECANCELED => return,
1197			_ => {
1198				sock.fail_rx(code);
1199				return;
1200			}
1201		}
1202		arm_recv(shared, sock);
1203		return;
1204	}
1205
1206	let received = match one {
1207		None => on_recv_multi(sock, cqe),
1208		Some(one) => on_recv_oneshot(*one, cqe),
1209	};
1210	match received {
1211		Ok((_, Some(queued))) => {
1212			sock.metrics.rx_receives.add(1);
1213			// A zero stride would be a kernel that reported a `UDP_GRO` size of
1214			// zero; count the receive as one datagram rather than dividing by it.
1215			sock.metrics
1216				.rx_datagrams
1217				.add(queued.len.div_ceil(queued.stride.max(1)) as u64);
1218			let mut rx = sock.rx.borrow_mut();
1219			rx.bufs[queued.bid as usize].outstanding += 1;
1220			rx.queue.push_back(queued);
1221			rx.waiters.wake();
1222		}
1223		// A dropped (truncated/malformed) receive: UDP loss semantics. The
1224		// buffer space it consumed still has to recycle.
1225		Ok((bid, None)) => {
1226			recycle_if_idle(&mut sock.rx.borrow_mut(), bid);
1227		}
1228		Err(code) => {
1229			sock.fail_rx(code);
1230			return;
1231		}
1232	}
1233	if terminal {
1234		arm_recv(shared, sock);
1235	}
1236}
1237
1238/// Bookkeeping for one multishot completion: the provided buffer it names,
1239/// consumed whole. Returns the buffer id and the packet, if any.
1240fn on_recv_multi(sock: &Rc<SockShared>, cqe: Cqe) -> Result<(u16, Option<Queued>), i32> {
1241	let mut rx = sock.rx.borrow_mut();
1242	let rx = &mut *rx;
1243	let Some(bid) = cqueue::buffer_select(cqe.flags) else {
1244		return Err(libc::EPROTO);
1245	};
1246	let len = cqe.result as usize;
1247	let buf = &mut rx.bufs[bid as usize];
1248	if len > buf.data.len() {
1249		return Err(libc::EPROTO);
1250	}
1251	// The completion consumed the buffer; it returns to the ring on recycle.
1252	buf.kernel_done = true;
1253
1254	let slice = &buf.data[..len];
1255	let Ok(out) = types::RecvMsgOut::parse(slice, &rx.hdr) else {
1256		tracing::warn!("dropping malformed multishot recvmsg completion");
1257		return Ok((bid, None));
1258	};
1259	if out.is_payload_truncated() || out.is_control_data_truncated() {
1260		tracing::warn!("dropping truncated receive (buffer tail too small for a full coalesce)");
1261		return Ok((bid, None));
1262	}
1263	let Some(from) = decode_addr(out.name_data()) else {
1264		tracing::warn!("dropping receive with an unparseable source address");
1265		return Ok((bid, None));
1266	};
1267	let payload = out.payload_data();
1268	if payload.is_empty() {
1269		return Ok((bid, None));
1270	}
1271	let meta = RecvMeta::parse(out.control_data());
1272	let payload_start = payload.as_ptr() as usize - buf.data.as_ptr() as usize;
1273	Ok((
1274		bid,
1275		Some(Queued {
1276			bid,
1277			start: payload_start,
1278			len: payload.len(),
1279			from,
1280			stride: meta.stride.unwrap_or(payload.len()),
1281			ecn: meta.ecn,
1282		}),
1283	))
1284}
1285
1286/// Bookkeeping for one oneshot completion: the claimed buffer holds only the
1287/// payload; address and control came back through our own msghdr. A dropped
1288/// packet leaves `claimed` for the caller's recycle to clear.
1289fn on_recv_oneshot(one: OneshotRecv, cqe: Cqe) -> Result<(u16, Option<Queued>), i32> {
1290	let bid = one.bid;
1291	let len = cqe.result as usize;
1292
1293	if one.hdr.msg_flags & (libc::MSG_TRUNC | libc::MSG_CTRUNC) != 0 {
1294		tracing::warn!("dropping truncated oneshot receive");
1295		return Ok((bid, None));
1296	}
1297	let name = {
1298		// SAFETY: the kernel wrote `msg_namelen` bytes of address.
1299		let ptr = (&raw const one.name).cast::<u8>();
1300		unsafe { std::slice::from_raw_parts(ptr, (one.hdr.msg_namelen as usize).min(NAME_LEN)) }
1301	};
1302	let Some(from) = decode_addr(name) else {
1303		tracing::warn!("dropping receive with an unparseable source address");
1304		return Ok((bid, None));
1305	};
1306	if len == 0 {
1307		return Ok((bid, None));
1308	}
1309	let control = &one.control.0[..one.hdr.msg_controllen.min(CONTROL_LEN)];
1310	let meta = RecvMeta::parse(control);
1311	// `claimed` stays set: the packet owns the buffer until released.
1312	Ok((
1313		bid,
1314		Some(Queued {
1315			bid,
1316			start: 0,
1317			len,
1318			from,
1319			stride: meta.stride.unwrap_or(len),
1320			ecn: meta.ecn,
1321		}),
1322	))
1323}
1324
1325/// Handle one send completion; the buffer lease releases when the last
1326/// completion drops its `SendOp`.
1327pub(crate) fn on_send(op: SendOp, cqe: Cqe) {
1328	if cqe.result < 0 {
1329		let code = -cqe.result;
1330		if code == libc::ECONNREFUSED {
1331			// ICMP unreachable noise; QUIC treats it as loss.
1332			tracing::debug!("send completed with ECONNREFUSED");
1333			return;
1334		}
1335		if code != libc::ECANCELED {
1336			op.sock.fail_tx(code);
1337		}
1338	} else if cqe.result as usize != op.expect {
1339		tracing::warn!(sent = cqe.result, expected = op.expect, "short UDP send");
1340		op.sock.fail_tx(libc::EIO);
1341	}
1342}
1343
1344/// What the kernel said about one receive, from its control buffer.
1345#[derive(Default)]
1346struct RecvMeta {
1347	/// The `UDP_GRO` segment size, if the receive was coalesced.
1348	stride: Option<usize>,
1349	/// The ECN codepoint from `IP_TOS` or `IPV6_TCLASS`, if marked.
1350	ecn: Option<Ecn>,
1351}
1352
1353impl RecvMeta {
1354	/// Walk the control messages; a malformed buffer ends the walk with what
1355	/// was read so far.
1356	fn parse(control: &[u8]) -> Self {
1357		let mut meta = Self::default();
1358		let header_len = unsafe { libc::CMSG_LEN(0) as usize };
1359		let mut offset = 0;
1360
1361		while offset + header_len <= control.len() {
1362			// SAFETY: bounds-checked read of a cmsghdr-sized prefix.
1363			let header = unsafe { control.as_ptr().add(offset).cast::<libc::cmsghdr>().read_unaligned() };
1364			let message_len = header.cmsg_len;
1365			if message_len < header_len || offset + message_len > control.len() {
1366				return meta;
1367			}
1368			let data = &control[offset + header_len..offset + message_len];
1369			match (header.cmsg_level, header.cmsg_type) {
1370				(libc::SOL_UDP, libc::UDP_GRO) => {
1371					meta.stride = read_int(data).and_then(|value| usize::try_from(value).ok());
1372				}
1373				// Linux reports the TOS byte itself, but the traffic class as an int.
1374				(libc::IPPROTO_IP, libc::IP_TOS) => {
1375					meta.ecn = data.first().and_then(|bits| Ecn::from_bits(*bits));
1376				}
1377				(libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => {
1378					meta.ecn = read_int(data).and_then(|value| Ecn::from_bits(value as u8));
1379				}
1380				_ => {}
1381			}
1382			// SAFETY: CMSG_SPACE is a pure size computation.
1383			let aligned = unsafe { libc::CMSG_SPACE((message_len - header_len) as _) as usize };
1384			offset = offset.saturating_add(aligned.max(header_len));
1385		}
1386
1387		meta
1388	}
1389}
1390
1391/// A control message's payload as the int the kernel wrote, if it is one.
1392fn read_int(data: &[u8]) -> Option<libc::c_int> {
1393	let bytes = data.get(..std::mem::size_of::<libc::c_int>())?;
1394	Some(libc::c_int::from_ne_bytes(bytes.try_into().ok()?))
1395}
1396
1397/// Turn a boolean socket option on.
1398fn set_option(io: &UdpSocket, level: libc::c_int, name: libc::c_int) -> io::Result<()> {
1399	let on: libc::c_int = 1;
1400	// SAFETY: valid fd, valid option buffer.
1401	let ret = unsafe {
1402		libc::setsockopt(
1403			io.as_raw_fd(),
1404			level,
1405			name,
1406			(&raw const on).cast(),
1407			std::mem::size_of::<libc::c_int>() as libc::socklen_t,
1408		)
1409	};
1410	match ret {
1411		0 => Ok(()),
1412		_ => Err(io::Error::last_os_error()),
1413	}
1414}
1415
1416/// Write `addr` into `out`, returning the length the kernel wants.
1417fn encode_addr(addr: SocketAddr, out: &mut libc::sockaddr_storage) -> libc::socklen_t {
1418	match addr {
1419		SocketAddr::V4(v4) => {
1420			let sin = libc::sockaddr_in {
1421				sin_family: libc::AF_INET as libc::sa_family_t,
1422				sin_port: v4.port().to_be(),
1423				sin_addr: libc::in_addr {
1424					s_addr: u32::from_ne_bytes(v4.ip().octets()),
1425				},
1426				sin_zero: [0; 8],
1427			};
1428			// SAFETY: sockaddr_in fits in sockaddr_storage.
1429			unsafe { (&raw mut *out).cast::<libc::sockaddr_in>().write(sin) };
1430			std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t
1431		}
1432		SocketAddr::V6(v6) => {
1433			let sin6 = libc::sockaddr_in6 {
1434				sin6_family: libc::AF_INET6 as libc::sa_family_t,
1435				sin6_port: v6.port().to_be(),
1436				sin6_flowinfo: v6.flowinfo(),
1437				sin6_addr: libc::in6_addr {
1438					s6_addr: v6.ip().octets(),
1439				},
1440				sin6_scope_id: v6.scope_id(),
1441			};
1442			// SAFETY: sockaddr_in6 fits in sockaddr_storage.
1443			unsafe { (&raw mut *out).cast::<libc::sockaddr_in6>().write(sin6) };
1444			std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t
1445		}
1446	}
1447}
1448
1449/// Parse a kernel-written socket address.
1450fn decode_addr(name: &[u8]) -> Option<SocketAddr> {
1451	if name.len() < std::mem::size_of::<libc::sa_family_t>() {
1452		return None;
1453	}
1454	const FAMILY_LEN: usize = std::mem::size_of::<libc::sa_family_t>();
1455	let mut family = [0u8; FAMILY_LEN];
1456	family.copy_from_slice(&name[..FAMILY_LEN]);
1457	match libc::sa_family_t::from_ne_bytes(family) as libc::c_int {
1458		libc::AF_INET if name.len() >= std::mem::size_of::<libc::sockaddr_in>() => {
1459			// SAFETY: length-checked unaligned read.
1460			let sin = unsafe { name.as_ptr().cast::<libc::sockaddr_in>().read_unaligned() };
1461			Some(SocketAddr::from((
1462				sin.sin_addr.s_addr.to_ne_bytes(),
1463				u16::from_be(sin.sin_port),
1464			)))
1465		}
1466		libc::AF_INET6 if name.len() >= std::mem::size_of::<libc::sockaddr_in6>() => {
1467			// SAFETY: length-checked unaligned read.
1468			let sin6 = unsafe { name.as_ptr().cast::<libc::sockaddr_in6>().read_unaligned() };
1469			// Keep the scope id: link-local replies are unroutable without it.
1470			Some(SocketAddr::V6(SocketAddrV6::new(
1471				sin6.sin6_addr.s6_addr.into(),
1472				u16::from_be(sin6.sin6_port),
1473				sin6.sin6_flowinfo,
1474				sin6.sin6_scope_id,
1475			)))
1476		}
1477		_ => None,
1478	}
1479}
1480
1481#[cfg(test)]
1482mod tests {
1483	use super::*;
1484	use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
1485
1486	/// Round-trip an address through the kernel wire encoding.
1487	fn roundtrip(addr: SocketAddr) -> Option<SocketAddr> {
1488		// SAFETY: all-zero is a valid sockaddr_storage.
1489		let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
1490		let len = encode_addr(addr, &mut storage) as usize;
1491		// SAFETY: encode_addr wrote `len` bytes into `storage`.
1492		let name = unsafe { std::slice::from_raw_parts((&raw const storage).cast::<u8>(), len) };
1493		decode_addr(name)
1494	}
1495
1496	#[test]
1497	fn addr_roundtrip_v4() {
1498		let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 7), 4443));
1499		assert_eq!(roundtrip(addr), Some(addr));
1500	}
1501
1502	#[test]
1503	fn addr_roundtrip_v6_keeps_scope_and_flow() {
1504		let ip = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
1505		let addr = SocketAddr::V6(SocketAddrV6::new(ip, 4443, 0x12345, 3));
1506		assert_eq!(roundtrip(addr), Some(addr));
1507	}
1508
1509	/// A receive pool with nothing allocated yet and a ring of its own.
1510	fn empty_rx() -> Rx {
1511		Rx {
1512			bufs: Vec::new(),
1513			// Never registered, so this one is ours alone to publish into.
1514			ring: Some(BufRing::new(64)),
1515			// SAFETY: all-zero is valid for `msghdr`, and nothing reads it here.
1516			hdr: Box::new(unsafe { std::mem::zeroed() }),
1517			queue: VecDeque::new(),
1518			waiters: kio::WaiterList::new(),
1519			armed: None,
1520			starved: false,
1521			error: None,
1522		}
1523	}
1524
1525	/// A recorded `ENOBUFS` outlives the buffer that recycled after it: the
1526	/// kernel ran the pool dry, so the pool is too shallow however full the
1527	/// ring looks by the time the re-arm gets to it. Growing off the ring's
1528	/// state alone leaves a bursting socket re-arming at its floor forever.
1529	#[test]
1530	fn a_recycled_buffer_does_not_mask_a_recorded_starvation() {
1531		let config = Config::default();
1532		let mut rx = empty_rx();
1533		grow_rx(&mut rx, &config);
1534		assert!(!should_grow(&rx, true), "a buffer is in the ring");
1535
1536		rx.starved = true;
1537		assert!(should_grow(&rx, true), "the kernel ran dry, recycle or not");
1538		assert!(should_grow(&rx, false), "and the oneshot path reads it too");
1539	}
1540
1541	/// A starved receive pool doubles into its ceiling, offering every new
1542	/// buffer to the kernel as it goes.
1543	#[test]
1544	fn the_receive_pool_doubles_to_its_ceiling() {
1545		let config = Config {
1546			rx_buffers_max: 40,
1547			..Default::default()
1548		};
1549		let mut rx = empty_rx();
1550
1551		for expected in [1u16, 2, 4, 8, 16, 32, 40] {
1552			assert!(grow_rx(&mut rx, &config), "growth stopped short of {expected}");
1553			assert_eq!(rx.bufs.len(), usize::from(expected));
1554			// Every buffer reaches the kernel exactly once.
1555			assert_eq!(rx.ring.as_ref().expect("ring").tail, expected);
1556		}
1557		assert!(!grow_rx(&mut rx, &config), "grew past the ceiling");
1558	}
1559}