1use std::{net::SocketAddr, sync::Arc};
4
5use n0_error::stack_error;
6use n0_future::time::Duration;
7use noq::{VarInt, crypto::rustls::QuicClientConfig};
8
9pub const ALPN_QUIC_ADDR_DISC: &[u8] = b"/iroh-qad/0";
11pub const QUIC_ADDR_DISC_CLOSE_CODE: VarInt = VarInt::from_u32(1);
13pub const QUIC_ADDR_DISC_CLOSE_REASON: &[u8] = b"finished";
15
16#[cfg(feature = "server")]
17pub(crate) mod server {
18 use n0_error::e;
19 use noq::{
20 ApplicationClose, ConnectionError,
21 crypto::rustls::{NoInitialCipherSuite, QuicServerConfig},
22 };
23 use tokio::task::JoinSet;
24 use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle};
25 use tracing::{Instrument, debug, info, info_span};
26
27 use super::*;
28 use crate::server::Metrics;
29
30 pub(crate) struct QuicServer {
31 bind_addr: SocketAddr,
32 cancel: CancellationToken,
33 handle: AbortOnDropHandle<()>,
34 }
35
36 #[allow(missing_docs)]
38 #[stack_error(derive, add_meta)]
39 #[non_exhaustive]
40 pub enum QuicSpawnError {
41 #[error("TLS not configured")]
42 TlsNotConfigured {},
43 #[error(transparent)]
44 NoInitialCipherSuite {
45 #[error(std_err, from)]
46 source: NoInitialCipherSuite,
47 },
48 #[error("Unable to spawn a QUIC endpoint server")]
49 EndpointServer {
50 #[error(std_err)]
51 source: std::io::Error,
52 },
53 #[error("Unable to get the local address from the endpoint")]
54 LocalAddr {
55 #[error(std_err)]
56 source: std::io::Error,
57 },
58 }
59
60 impl QuicServer {
61 pub(crate) fn handle(&self) -> ServerHandle {
66 ServerHandle {
67 cancel_token: self.cancel.clone(),
68 }
69 }
70
71 pub(crate) fn task_handle(&mut self) -> &mut AbortOnDropHandle<()> {
77 &mut self.handle
78 }
79
80 pub(crate) fn bind_addr(&self) -> SocketAddr {
82 self.bind_addr
83 }
84
85 pub(crate) fn spawn(
98 bind_addr: SocketAddr,
99 mut server_config: rustls::ServerConfig,
100 metrics: Arc<Metrics>,
101 ) -> Result<Self, QuicSpawnError> {
102 server_config.alpn_protocols = vec![crate::quic::ALPN_QUIC_ADDR_DISC.to_vec()];
103 let server_config = QuicServerConfig::try_from(server_config)?;
104 let mut server_config = noq::ServerConfig::with_crypto(Arc::new(server_config));
105 let transport_config =
106 Arc::get_mut(&mut server_config.transport).expect("not used yet");
107 transport_config
108 .max_concurrent_uni_streams(0_u8.into())
109 .max_concurrent_bidi_streams(0_u8.into())
110 .send_observed_address_reports(true);
112
113 let endpoint = noq::Endpoint::server(server_config, bind_addr)
114 .map_err(|err| e!(QuicSpawnError::EndpointServer, err))?;
115 let bind_addr = endpoint
116 .local_addr()
117 .map_err(|err| e!(QuicSpawnError::LocalAddr, err))?;
118
119 info!(?bind_addr, "QUIC server listening on");
120
121 let cancel = CancellationToken::new();
122 let cancel_accept_loop = cancel.clone();
123
124 let task = tokio::task::spawn(
125 async move {
126 let mut set = JoinSet::new();
127 debug!("waiting for connections...");
128 loop {
129 tokio::select! {
130 biased;
131 _ = cancel_accept_loop.cancelled() => {
132 break;
133 }
134 Some(res) = set.join_next() => {
135 if let Err(err) = res {
136 if err.is_panic() {
137 panic!("quic task panicked: {err:#?}");
138 } else {
139 debug!("quic task cancelled: {err:#?}");
140 }
141 }
142 }
143 res = endpoint.accept() => match res {
144 Some(incoming) => {
145 let remote_addr = incoming.remote_address();
146 set.spawn(
147 handle_connection(incoming, metrics.clone()).instrument(info_span!("qad-conn", %remote_addr))
148 ); }
149 None => {
150 debug!("endpoint closed");
151 break;
152 }
153 }
154 }
155 }
156 endpoint.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);
159 endpoint.wait_idle().await;
160
161 set.abort_all();
164 while !set.is_empty() {
165 _ = set.join_next().await;
166 }
167
168 debug!("quic endpoint has been shutdown.");
169 }
170 .instrument(info_span!("quic-endpoint")),
171 );
172 Ok(Self {
173 bind_addr,
174 cancel,
175 handle: AbortOnDropHandle::new(task),
176 })
177 }
178
179 pub(crate) async fn shutdown(mut self) {
182 self.cancel.cancel();
183 if !self.task_handle().is_finished() {
184 _ = self.task_handle().await;
187 }
188 }
189 }
190
191 #[derive(Debug, Clone)]
195 pub(crate) struct ServerHandle {
196 cancel_token: CancellationToken,
197 }
198
199 impl ServerHandle {
200 pub(crate) fn shutdown(&self) {
202 self.cancel_token.cancel()
203 }
204 }
205
206 async fn handle_connection(
208 incoming: noq::Incoming,
209 metrics: Arc<Metrics>,
210 ) -> Result<(), ConnectionError> {
211 metrics.qad_incoming.inc();
212 debug!("incoming");
213 let connection = match incoming.await {
214 Ok(conn) => conn,
215 Err(e) => {
216 debug!("establishing failed: {e:#}");
217 metrics.qad_incoming_error.inc();
218 return Err(e);
219 }
220 };
221 metrics.qad_connections.inc();
222 debug!("established");
223 let connection_err = connection.closed().await;
225 metrics.qad_connections_closed.inc();
226 match connection_err {
227 noq::ConnectionError::ApplicationClosed(ApplicationClose { error_code, .. })
228 if error_code == QUIC_ADDR_DISC_CLOSE_CODE =>
229 {
230 debug!("peer disconnected");
231 Ok(())
232 }
233 _ => {
234 debug!("peer disconnected with {connection_err:#}");
235 metrics.qad_connections_errored.inc();
236 Err(connection_err)
237 }
238 }
239 }
240}
241
242#[allow(missing_docs)]
244#[stack_error(derive, add_meta, from_sources, std_sources)]
245#[non_exhaustive]
246pub enum Error {
247 #[error(transparent)]
248 Connect {
249 #[error(std_err)]
250 source: noq::ConnectError,
251 },
252 #[error(transparent)]
253 Connection {
254 #[error(std_err)]
255 source: noq::ConnectionError,
256 },
257 NoObservedAddr,
258}
259
260#[derive(Debug, Clone)]
262pub struct QuicClient {
263 ep: noq::Endpoint,
265 client_config: noq::ClientConfig,
267}
268
269impl QuicClient {
270 pub fn new(ep: noq::Endpoint, mut client_config: rustls::ClientConfig) -> Self {
273 client_config.alpn_protocols = vec![ALPN_QUIC_ADDR_DISC.into()];
275 let mut client_config = noq::ClientConfig::new(Arc::new(
278 QuicClientConfig::try_from(client_config).expect("known ciphersuite"),
279 ));
280
281 let mut transport = noq_proto::TransportConfig::default();
283 transport.initial_rtt(Duration::from_millis(111));
294 transport.receive_observed_address_reports(true);
295
296 transport.keep_alive_interval(Some(Duration::from_secs(25)));
298 transport.max_idle_timeout(Some(
299 Duration::from_secs(35).try_into().expect("known value"),
300 ));
301 client_config.transport_config(Arc::new(transport));
302
303 Self { ep, client_config }
304 }
305
306 #[cfg(all(test, feature = "server"))]
313 async fn get_addr_and_latency(
314 &self,
315 server_addr: SocketAddr,
316 host: &str,
317 ) -> Result<(SocketAddr, std::time::Duration), Error> {
318 use n0_future::StreamExt;
319 use noq_proto::PathId;
320
321 let connecting = self
322 .ep
323 .connect_with(self.client_config.clone(), server_addr, host);
324 let conn = connecting?.await?;
325 let mut external_addresses = conn.observed_external_addr();
326 let Some(mut observed_addr) = external_addresses.next().await else {
342 n0_error::bail!(Error::NoObservedAddr);
343 };
344 observed_addr = SocketAddr::new(observed_addr.ip().to_canonical(), observed_addr.port());
347 let latency = conn.rtt(PathId::ZERO).unwrap_or_default();
348 conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);
350 Ok((observed_addr, latency))
351 }
352
353 pub async fn create_conn(
355 &self,
356 server_addr: SocketAddr,
357 host: &str,
358 ) -> Result<noq::Connection, Error> {
359 let config = self.client_config.clone();
360 let connecting = self.ep.connect_with(config, server_addr, host);
361 let conn = connecting?.await?;
362 Ok(conn)
363 }
364}
365
366#[cfg(all(test, feature = "server", with_crypto_provider))]
367mod tests {
368 use std::{net::Ipv4Addr, sync::Arc};
369
370 use n0_error::{Result, StdResultExt};
371 use n0_future::{
372 task::AbortOnDropHandle,
373 time::{self, Instant},
374 };
375 use n0_tracing_test::traced_test;
376 use noq::crypto::rustls::QuicServerConfig;
377 use tracing::{Instrument, debug, info, info_span};
378 use webpki_types::PrivatePkcs8KeyDer;
379
380 use super::*;
381
382 #[tokio::test]
383 #[traced_test]
384 #[cfg(feature = "test-utils")]
385 async fn quic_endpoint_basic() -> Result {
386 use super::server::QuicServer;
387
388 let host: Ipv4Addr = "127.0.0.1".parse().unwrap();
389 let (_, server_config) = super::super::server::testing::self_signed_tls_certs_and_config();
391 let bind_addr = SocketAddr::new(host.into(), 0);
392 let quic_server = QuicServer::spawn(bind_addr, server_config, Default::default())?;
393
394 let client_endpoint =
396 noq::Endpoint::client(SocketAddr::new(host.into(), 0)).std_context("client")?;
397 let client_addr = client_endpoint.local_addr().std_context("local addr")?;
398
399 let client_config = crate::tls::make_dangerous_client_config();
402 let quic_client = QuicClient::new(client_endpoint.clone(), client_config);
403
404 let (addr, _latency) = quic_client
405 .get_addr_and_latency(quic_server.bind_addr(), &host.to_string())
406 .await?;
407
408 client_endpoint.wait_idle().await;
410 quic_server.shutdown().await;
412
413 assert_eq!(client_addr, addr);
414 Ok(())
415 }
416
417 #[tokio::test(start_paused = true)]
418 #[traced_test]
419 async fn test_qad_client_closes_unresponsive_fast() -> Result {
420 let client_endpoint = noq::Endpoint::client(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0))
422 .std_context("client")?;
423
424 let server_socket =
426 tokio::net::UdpSocket::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0))
427 .await
428 .std_context("bind")?;
429 let server_addr = server_socket.local_addr().std_context("local addr")?;
430
431 let client_config = crate::tls::make_dangerous_client_config();
434 let quic_client = QuicClient::new(client_endpoint.clone(), client_config);
435
436 let task = AbortOnDropHandle::new(tokio::spawn({
438 async move {
439 quic_client
440 .get_addr_and_latency(server_addr, "localhost")
441 .await
442 }
443 }));
444
445 tokio::time::sleep(Duration::from_millis(1000)).await;
447 assert!(!task.is_finished());
448
449 let before = Instant::now();
451 client_endpoint.close(0u32.into(), b"byeeeee");
452 client_endpoint.wait_idle().await;
453 let time = Instant::now().duration_since(before);
454
455 assert_eq!(time, Duration::from_millis(999));
456
457 Ok(())
458 }
459
460 #[tokio::test]
467 #[traced_test]
468 async fn test_qad_connect_delayed() -> Result {
469 let socket = tokio::net::UdpSocket::bind(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0))
472 .await
473 .std_context("bind")?;
474 let server_addr = socket.local_addr().std_context("local addr")?;
475 info!(addr = ?server_addr, "server socket bound");
476
477 let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])
479 .std_context("self signed")?;
480 let key = PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der());
481 let mut server_crypto = rustls::ServerConfig::builder_with_provider(Arc::new(
482 rustls::crypto::ring::default_provider(),
483 ))
484 .with_safe_default_protocol_versions()
485 .std_context("crypto provider")?
486 .with_no_client_auth()
487 .with_single_cert(vec![cert.cert.into()], key.into())
488 .std_context("tls")?;
489 server_crypto.key_log = Arc::new(rustls::KeyLogFile::new());
490 server_crypto.alpn_protocols = vec![ALPN_QUIC_ADDR_DISC.to_vec()];
491 let mut server_config = noq::ServerConfig::with_crypto(Arc::new(
492 QuicServerConfig::try_from(server_crypto).std_context("config")?,
493 ));
494 let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
495 transport_config.send_observed_address_reports(true);
496
497 let start = Instant::now();
498 let server_task = tokio::spawn(
499 async move {
500 info!("Dropping all packets");
501 time::timeout(Duration::from_secs(2), async {
502 let mut buf = [0u8; 1500];
503 loop {
504 let (len, src) = socket.recv_from(&mut buf).await.unwrap();
505 debug!(%len, ?src, "Dropped a packet");
506 }
507 })
508 .await
509 .ok();
510 info!("starting server");
511 let server = noq::Endpoint::new(
512 Default::default(),
513 Some(server_config),
514 socket.into_std().unwrap(),
515 Arc::new(noq::TokioRuntime),
516 )
517 .std_context("endpoint new")?;
518 info!("accepting conn");
519 let incoming = server.accept().await.expect("missing conn");
520 info!("incoming!");
521 let conn = incoming.await.std_context("incoming")?;
522 conn.closed().await;
523 server.wait_idle().await;
524 n0_error::Ok(())
525 }
526 .instrument(info_span!("server")),
527 );
528 let server_task = AbortOnDropHandle::new(server_task);
529
530 info!("starting client");
531 let client_endpoint = noq::Endpoint::client(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0))
532 .std_context("client")?;
533
534 let client_config = crate::tls::make_dangerous_client_config();
537 let quic_client = QuicClient::new(client_endpoint.clone(), client_config);
538
539 info!("making QAD request");
541 let (addr, latency) = time::timeout(
542 Duration::from_secs(10),
543 quic_client.get_addr_and_latency(server_addr, "localhost"),
544 )
545 .await
546 .std_context("timeout")??;
547 let duration = start.elapsed();
548 info!(?duration, ?addr, ?latency, "QAD succeeded");
549 assert!(duration >= Duration::from_secs(1));
550
551 time::timeout(Duration::from_secs(10), server_task)
552 .await
553 .std_context("timeout")?
554 .std_context("server task")??;
555
556 Ok(())
557 }
558}