Skip to main content

moq_uring/quic/noq/
endpoint.rs

1//! The noq endpoint: one socket, many noq-proto connections.
2//!
3//! noq-proto's own [`moq_noq_proto::Endpoint`] is the routing table: it parses
4//! each datagram, hands it to the connection its destination id names, mints
5//! and retires ids as peers consume them, and answers an unsupported version
6//! itself. What is left for us is the socket, the accept backlog, and a driver
7//! task per connection. Everything policy-shaped (the backlog, the shard
8//! steering, the id length) lives in [`super::super::endpoint`], which is
9//! shared with the rest of the endpoint implementation.
10
11use std::cell::{Cell, RefCell};
12use std::collections::VecDeque;
13use std::net::SocketAddr;
14use std::rc::Rc;
15use std::sync::Arc;
16use std::task::Poll;
17use std::time::Instant;
18
19use bytes::BytesMut;
20use moq_noq_proto::{ConnectionHandle, DatagramEvent, Incoming, Transmit};
21use rustc_hash::FxHashMap;
22
23use super::super::{Error, endpoint::Config};
24use super::connection;
25use crate::quic::Connection;
26use crate::shared::Shared;
27use crate::udp;
28use crate::worker::Owner;
29
30/// The accept side: connections whose handshake finished and nobody has
31/// claimed yet.
32struct Accepting {
33	queue: VecDeque<Connection>,
34}
35
36/// State shared by the handles, the demux task, and the per-connection
37/// teardown tasks.
38pub(crate) struct Inner {
39	/// The socket's worker, where every task of this endpoint runs.
40	owner: Owner,
41	socket: Rc<udp::Socket>,
42	local: SocketAddr,
43	/// The routing table, and the server configuration it accepts with.
44	endpoint: RefCell<moq_noq_proto::Endpoint>,
45	accepting: RefCell<Option<Accepting>>,
46	/// Every live connection, looked up per received datagram.
47	///
48	/// FxHash rather than SipHash: noq-proto hands out the handle, and it
49	/// owns connection-id routing (and its hasher choices) itself, so nothing
50	/// a peer picks reaches this map.
51	conns: RefCell<FxHashMap<ConnectionHandle, connection::Shared>>,
52	/// Incoming handshakes in flight. Together with the accept queue this is
53	/// bounded by [`Config::backlog`].
54	pending: Cell<usize>,
55	backlog: usize,
56	/// Live [`Endpoint`] handles; at zero the endpoint winds down (no new
57	/// accepts or dials, existing connections served until they end).
58	handles: Cell<usize>,
59	/// The socket's terminal error, if it died under us.
60	closed: RefCell<Option<Error>>,
61	/// Wakes the demux task to re-check its exit condition.
62	task_waiters: RefCell<kio::WaiterList>,
63	accept_waiters: RefCell<kio::WaiterList>,
64}
65
66/// A QUIC endpoint on one worker socket: accept, dial, or both.
67///
68/// Created by [`new`](Endpoint::new); clones share the endpoint. Dropping the
69/// last handle stops new accepts and dials, but connections already
70/// established (or still handshaking) are served until they end; the socket
71/// closes when the last one does.
72pub struct Endpoint {
73	inner: Rc<Inner>,
74}
75
76impl Endpoint {
77	/// Serve `socket` on the worker that adopted it.
78	///
79	/// The socket names the worker: the demux task and every connection
80	/// driver run there, and a socket adopted as a reuseport member steers
81	/// the ids this endpoint issues to itself. Refused once that worker has
82	/// been dropped, since nothing would ever drive the endpoint.
83	pub fn new(socket: udp::Socket, config: Config) -> Result<Self, Error> {
84		let owner = socket.owner();
85		let Some(handle) = owner.handle() else {
86			return Err(Error::Io(Shared::gone_error().to_string()));
87		};
88		let local = socket.local_addr().map_err(|err| Error::Io(err.to_string()))?;
89		let server = match &config.server {
90			Some(server) => {
91				let mut server = super::server_config(server)?;
92				// noq buffers half-open handshakes itself, so it enforces
93				// the same bound the accept queue does.
94				server.max_incoming(config.backlog);
95				Some(Arc::new(server))
96			}
97			None => None,
98		};
99		let accepting = server.is_some().then(|| Accepting { queue: VecDeque::new() });
100		// MTU discovery is off (the GSO pool sends fixed SEGMENT datagrams),
101		// so the endpoint has no reason to allow it either.
102		let endpoint = moq_noq_proto::Endpoint::new(super::endpoint_config(socket.shard())?, server, false);
103
104		let inner = Rc::new(Inner {
105			owner,
106			socket: Rc::new(socket),
107			local,
108			endpoint: RefCell::new(endpoint),
109			accepting: RefCell::new(accepting),
110			conns: RefCell::new(FxHashMap::default()),
111			pending: Cell::new(0),
112			backlog: config.backlog,
113			handles: Cell::new(1),
114			closed: RefCell::new(None),
115			task_waiters: RefCell::new(kio::WaiterList::new()),
116			accept_waiters: RefCell::new(kio::WaiterList::new()),
117		});
118
119		let task = inner.clone();
120		handle.spawn(async move { kio::wait(|waiter| task.poll_run(waiter)).await });
121
122		Ok(Self { inner })
123	}
124
125	/// The socket's bound local address.
126	pub fn local_addr(&self) -> SocketAddr {
127		self.inner.local
128	}
129
130	/// The next handshaken incoming connection.
131	///
132	/// Fails immediately on an endpoint built without a
133	/// [`server`](Config::server) configuration, and with the socket's error
134	/// once the endpoint has died, its worker included.
135	pub async fn accept(&self) -> Result<Connection, Error> {
136		kio::wait(|waiter| {
137			if let Some(err) = self.inner.closed() {
138				return Poll::Ready(Err(err));
139			}
140			let mut accepting = self.inner.accepting.borrow_mut();
141			let Some(accepting) = accepting.as_mut() else {
142				return Poll::Ready(Err(Error::NotServer));
143			};
144			if let Some(conn) = accepting.queue.pop_front() {
145				return Poll::Ready(Ok(conn));
146			}
147			waiter.register(&mut self.inner.accept_waiters.borrow_mut());
148			Poll::Pending
149		})
150		.await
151	}
152
153	/// Dial [`Config::peer`](crate::quic::client::Config::peer) through this
154	/// endpoint's socket, driving the handshake to completion.
155	pub async fn connect(&self, config: &crate::quic::client::Config) -> Result<Connection, Error> {
156		if let Some(err) = self.inner.closed() {
157			return Err(err);
158		}
159		let client = super::client_config(config)?;
160		let (key, conn) = self
161			.inner
162			.endpoint
163			.borrow_mut()
164			.connect(Instant::now(), client, config.peer, &config.server_name)
165			.map_err(|err| Error::Quic(err.to_string()))?;
166		let shared = self.inner.launch(key, conn);
167		// Flush the Initial flight; the demux task takes it from here.
168		shared.kick();
169		connection::establish(shared).await
170	}
171}
172
173impl Clone for Endpoint {
174	fn clone(&self) -> Self {
175		self.inner.handles.set(self.inner.handles.get() + 1);
176		Self {
177			inner: self.inner.clone(),
178		}
179	}
180}
181
182impl Drop for Endpoint {
183	fn drop(&mut self) {
184		let handles = self.inner.handles.get() - 1;
185		self.inner.handles.set(handles);
186		if handles > 0 {
187			return;
188		}
189		// Nobody will ever claim the queued connections; close them rather
190		// than leaving their drivers to idle out.
191		if let Some(accepting) = self.inner.accepting.borrow_mut().as_mut() {
192			for mut conn in accepting.queue.drain(..) {
193				web_transport_trait::poll::Session::close(&mut conn, 0, "endpoint closed");
194			}
195		}
196		self.inner.task_waiters.borrow_mut().wake();
197	}
198}
199
200impl std::fmt::Debug for Endpoint {
201	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202		f.debug_struct("Endpoint")
203			.field("local", &self.inner.local)
204			.field("conns", &self.inner.conns.borrow().len())
205			.finish()
206	}
207}
208
209impl Inner {
210	/// Why nothing new can start here: the socket's terminal error, or the
211	/// worker being gone, which no task would ever report since none runs.
212	fn closed(&self) -> Option<Error> {
213		if let Some(err) = &*self.closed.borrow() {
214			return Some(err.clone());
215		}
216		self.owner
217			.handle()
218			.is_none()
219			.then(|| Error::Io(Shared::gone_error().to_string()))
220	}
221
222	/// The demux task: receive, route, and stop once nothing needs us.
223	fn poll_run(self: &Rc<Self>, waiter: &kio::Waiter) -> Poll<()> {
224		// Handle drops and connection teardowns re-check the exit condition.
225		waiter.register(&mut self.task_waiters.borrow_mut());
226
227		loop {
228			if self.handles.get() == 0 && self.conns.borrow().is_empty() {
229				return Poll::Ready(());
230			}
231			match self.socket.poll_recv(waiter) {
232				Poll::Ready(Ok(mut packet)) => self.demux(&mut packet),
233				Poll::Ready(Err(err)) => {
234					self.fail(Error::Io(err.to_string()));
235					return Poll::Ready(());
236				}
237				Poll::Pending => return Poll::Pending,
238			}
239		}
240	}
241
242	/// Route every datagram in one receive, then kick the connections fed.
243	fn demux(self: &Rc<Self>, packet: &mut udp::Packet) {
244		let from = packet.from();
245		let ecn = packet.ecn().map(super::ecn_to_noq);
246		// Where the endpoint writes its own answers (version negotiation,
247		// retry, a refusal), reused across the whole receive.
248		let mut buf = Vec::new();
249
250		// The connections this packet fed; almost always exactly one (a GRO
251		// coalesce is a single source, and ids rarely change mid-burst).
252		let mut fed: Vec<ConnectionHandle> = Vec::new();
253
254		for segment in packet.segments() {
255			buf.clear();
256			// The socket does not report which local address a datagram
257			// arrived on, so the four-tuple stays half known, exactly as it
258			// does on a platform without `IP_PKTINFO`.
259			let event = self.endpoint.borrow_mut().handle(
260				Instant::now(),
261				from.into(),
262				ecn,
263				BytesMut::from(&segment[..]),
264				&mut buf,
265			);
266			match event {
267				Some(DatagramEvent::ConnectionEvent(key, event)) => {
268					let conn = self.conns.borrow().get(&key).cloned();
269					// A connection whose driver has already exited is not one
270					// we can feed.
271					if let Some(conn) = conn {
272						conn.conn.borrow_mut().handle_event(event);
273						if !fed.contains(&key) {
274							fed.push(key);
275						}
276					}
277				}
278				Some(DatagramEvent::NewConnection(incoming)) => {
279					if let Some(key) = self.greet(incoming, &mut buf)
280						&& !fed.contains(&key)
281					{
282						fed.push(key);
283					}
284				}
285				// A dial-only endpoint owns no inbound policy, so it spends
286				// nothing on strangers: no version negotiation, and no
287				// stateless reset either.
288				Some(DatagramEvent::Response(transmit)) if self.accepting.borrow().is_some() => {
289					self.respond(&transmit, &buf)
290				}
291				Some(DatagramEvent::Response(_)) => {}
292				None => {}
293			}
294		}
295
296		for key in fed {
297			let conn = self.conns.borrow().get(&key).cloned();
298			if let Some(conn) = conn {
299				conn.kick();
300			}
301		}
302	}
303
304	/// A datagram starting a handshake: accept it, or drop it and let the
305	/// peer retransmit.
306	fn greet(self: &Rc<Self>, incoming: Incoming, buf: &mut Vec<u8>) -> Option<ConnectionHandle> {
307		// Nobody left to claim the connection. This also suppresses the
308		// stateless responses of a socket nobody is serving from.
309		if self.handles.get() == 0 {
310			self.endpoint.borrow_mut().ignore(incoming);
311			return None;
312		}
313		// Bound every connection the application has not claimed, whether its
314		// handshake is still pending or it is already queued for accept.
315		let queued = self
316			.accepting
317			.borrow()
318			.as_ref()
319			.map(|accepting| accepting.queue.len())
320			.unwrap_or(0);
321		if self.pending.get() + queued >= self.backlog {
322			tracing::debug!(from = %incoming.remote_address(), "dropping a handshake over the backlog");
323			// Dropped rather than refused: a real peer retransmits and lands
324			// once the application drains the backlog.
325			self.endpoint.borrow_mut().ignore(incoming);
326			return None;
327		}
328
329		buf.clear();
330		let accepted = self.endpoint.borrow_mut().accept(incoming, Instant::now(), buf, None);
331		let (key, conn) = match accepted {
332			Ok(accepted) => accepted,
333			Err(err) => {
334				tracing::debug!(err = %err.cause, "failed to accept a connection");
335				if let Some(transmit) = err.response {
336					self.respond(&transmit, buf);
337				}
338				return None;
339			}
340		};
341
342		let shared = self.launch(key, conn);
343
344		// Hand the connection over once (if) its handshake completes.
345		self.pending.set(self.pending.get() + 1);
346		let inner = self.clone();
347		self.owner.spawn(async move {
348			let outcome = connection::establish(shared).await;
349			inner.pending.set(inner.pending.get() - 1);
350			let mut conn = match outcome {
351				Ok(conn) => conn,
352				Err(err) => {
353					tracing::debug!(%err, "incoming handshake failed");
354					return;
355				}
356			};
357			// The last handle may have left while we handshook; see Drop.
358			if inner.handles.get() == 0 {
359				web_transport_trait::poll::Session::close(&mut conn, 0, "endpoint closed");
360				return;
361			}
362			{
363				let mut accepting = inner.accepting.borrow_mut();
364				let accepting = accepting.as_mut().expect("accepted without a server config");
365				accepting.queue.push_back(conn);
366			}
367			inner.accept_waiters.borrow_mut().wake();
368		});
369
370		Some(key)
371	}
372
373	/// Send one packet the endpoint itself produced, from `buf`.
374	///
375	/// Best effort: under send backpressure the packet is dropped and the
376	/// peer's retransmit tries again.
377	fn respond(&self, transmit: &Transmit, buf: &[u8]) {
378		let Poll::Ready(Ok(mut tx)) = self.socket.poll_acquire(&kio::Waiter::noop()) else {
379			return;
380		};
381		if transmit.size > tx.len() {
382			tracing::debug!(size = transmit.size, "dropping an oversized endpoint response");
383			return;
384		}
385		tx[..transmit.size].copy_from_slice(&buf[..transmit.size]);
386		let transmit = udp::Transmit {
387			to: transmit.destination,
388			len: transmit.size,
389			segment: transmit.segment_size.unwrap_or(transmit.size),
390			ecn: transmit.ecn.map(super::ecn_from_noq),
391		};
392		if let Err(err) = tx.send(transmit) {
393			tracing::debug!(%err, "failed to send an endpoint response");
394		}
395	}
396
397	/// Register `conn`, spawn its driver, and arrange its teardown.
398	fn launch(self: &Rc<Self>, key: ConnectionHandle, conn: moq_noq_proto::Connection) -> connection::Shared {
399		let (shared, driver) = connection::launch(&self.owner, self.socket.clone(), Rc::downgrade(self), key, conn);
400		self.conns.borrow_mut().insert(key, shared.clone());
401
402		let inner = self.clone();
403		self.owner.spawn(async move {
404			driver.await;
405			inner.release(key);
406		});
407		shared
408	}
409
410	/// The events a connection and the endpoint trade: fresh connection ids,
411	/// retirements, and the drain that frees the connection's slot.
412	pub(crate) fn on_connection_event(
413		&self,
414		key: ConnectionHandle,
415		event: moq_noq_proto::EndpointEvent,
416	) -> Option<moq_noq_proto::ConnectionEvent> {
417		self.endpoint.borrow_mut().handle_event(key, event)
418	}
419
420	/// A connection's driver ended; forget it.
421	fn release(&self, key: ConnectionHandle) {
422		self.conns.borrow_mut().remove(&key);
423		// The demux task may be waiting on us to exit.
424		self.task_waiters.borrow_mut().wake();
425	}
426
427	/// The socket died: everything on it fails with `err`.
428	fn fail(&self, err: Error) {
429		*self.closed.borrow_mut() = Some(err.clone());
430		let conns: Vec<connection::Shared> = self.conns.borrow().values().cloned().collect();
431		for conn in conns {
432			conn.fail(err.clone());
433		}
434		self.accept_waiters.borrow_mut().wake();
435	}
436}