iroh_relay/
server.rs

1//! A fully-fledged iroh-relay server over HTTP or HTTPS.
2//!
3//! This module provides an API to run a full fledged iroh-relay server.  It is primarily
4//! used by the `iroh-relay` binary in this crate.  It can be used to run a relay server in
5//! other locations however.
6//!
7//! This code is fully written in a form of structured-concurrency: every spawned task is
8//! always attached to a handle and when the handle is dropped the tasks abort.  So tasks
9//! can not outlive their handle.  It is also always possible to await for completion of a
10//! task.  Some tasks additionally have a method to do graceful shutdown.
11//!
12//! The relay server hosts the following services:
13//!
14//! - HTTPS `/relay`: The main URL endpoint to which clients connect and sends traffic over.
15//! - HTTPS `/ping`: Used for net_report probes.
16//! - HTTPS `/generate_204`: Used for net_report probes.
17
18use std::{
19    borrow::Cow,
20    future::Future,
21    net::SocketAddr,
22    num::NonZeroU32,
23    path::PathBuf,
24    pin::Pin,
25    sync::{
26        Arc,
27        atomic::{AtomicU64, Ordering},
28    },
29    time::Duration,
30};
31
32use derive_more::Debug;
33use http::{
34    HeaderMap, HeaderValue, Method, Request, Response, StatusCode,
35    header::{AUTHORIZATION, InvalidHeaderValue},
36    response::Builder as ResponseBuilder,
37};
38use http_body_util::Full;
39use hyper::body::Incoming;
40use iroh_base::EndpointId;
41#[cfg(feature = "test-utils")]
42use iroh_base::RelayUrl;
43use n0_error::{e, stack_error};
44use n0_future::{StreamExt, task::AbortOnDropHandle};
45use rustls::server::WantsServerCert;
46use serde::Serialize;
47use tokio::{
48    net::TcpListener,
49    task::{JoinError, JoinSet},
50};
51use tokio_rustls_acme::{
52    acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY},
53    caches::DirCache,
54};
55use tracing::{Instrument, debug, error, info, info_span, instrument};
56
57use self::http_server::{BytesBody, HyperError, HyperResult};
58use crate::{
59    defaults::DEFAULT_KEY_CACHE_CAPACITY,
60    http::{AUTH_TOKEN_URL_QUERY_PARAM, ProtocolVersion, RELAY_PROBE_PATH},
61    quic::server::{QuicServer, QuicSpawnError, ServerHandle as QuicServerHandle},
62    tls::CaTlsConfig,
63};
64
65pub mod client;
66pub mod clients;
67pub mod http_server;
68mod metrics;
69pub(crate) mod resolver;
70pub mod streams;
71#[cfg(feature = "test-utils")]
72pub mod testing;
73
74pub use self::{
75    http_server::{Handlers, RelayService},
76    metrics::{Metrics, RelayMetrics},
77    resolver::{DEFAULT_CERT_RELOAD_INTERVAL, reloading_resolver},
78};
79
80const NO_CONTENT_CHALLENGE_HEADER: &str = "X-Iroh-Challenge";
81const NO_CONTENT_RESPONSE_HEADER: &str = "X-Iroh-Response";
82const NOTFOUND: &[u8] = b"Not Found";
83const ROBOTS_TXT: &[u8] = b"User-agent: *\nDisallow: /\n";
84const INDEX: &[u8] = br#"<html><body>
85<h1>Iroh Relay</h1>
86<p>
87  This is an <a href="https://iroh.computer/">Iroh</a> Relay server.
88</p>
89"#;
90const TLS_HEADERS: [(&str, &str); 2] = [
91    (
92        "Strict-Transport-Security",
93        "max-age=63072000; includeSubDomains",
94    ),
95    (
96        "Content-Security-Policy",
97        "default-src 'none'; frame-ancestors 'none'; form-action 'none'; base-uri 'self'; block-all-mixed-content; plugin-types 'none'",
98    ),
99];
100
101/// Creates a new [`BytesBody`] with no content.
102fn body_empty() -> BytesBody {
103    Box::new(http_body_util::Full::new(hyper::body::Bytes::new()))
104}
105
106/// Configuration for the full Relay.
107///
108/// Be aware the generic parameters are for when using the Let's Encrypt TLS configuration.
109/// If not used dummy ones need to be provided, e.g. `ServerConfig::<(), ()>::default()`.
110#[derive(Debug, Default)]
111#[non_exhaustive]
112pub struct ServerConfig {
113    /// Configuration for the Relay server, disabled if `None`.
114    pub relay: Option<RelayConfig>,
115    /// Configuration for the QUIC server, disabled if `None`.
116    pub quic: Option<QuicConfig>,
117    /// Socket to serve metrics on.
118    #[cfg(feature = "metrics")]
119    pub metrics_addr: Option<SocketAddr>,
120}
121
122/// Configuration for the Relay HTTP and HTTPS server.
123///
124/// This includes the HTTP services hosted by the Relay server, the Relay `/relay` HTTP
125/// endpoint is only one of the services served.
126#[derive(Debug)]
127#[non_exhaustive]
128pub struct RelayConfig {
129    /// The socket address on which the Relay HTTP server should bind.
130    ///
131    /// Normally you'd choose port `80`.  The bind address for the HTTPS server is
132    /// configured in [`RelayConfig::tls`].
133    ///
134    /// If [`RelayConfig::tls`] is `None` then this serves all the HTTP services without
135    /// TLS.
136    pub http_bind_addr: SocketAddr,
137    /// TLS configuration for the HTTPS server.
138    ///
139    /// If *None* all the HTTP services that would be served here are served from
140    /// [`RelayConfig::http_bind_addr`].
141    pub tls: Option<TlsConfig>,
142    /// Rate limits.
143    pub limits: Limits,
144    /// Key cache capacity.
145    pub key_cache_capacity: Option<usize>,
146    /// Access control for incoming connections.
147    pub access: Arc<dyn DynAccessControl>,
148}
149
150impl RelayConfig {
151    /// Creates a new [`RelayConfig`] bound to `http_bind_addr` with default settings.
152    ///
153    /// TLS is disabled, default [`Limits`] are used, the key cache capacity is unset, and
154    /// access defaults to [`AllowAll`]. Adjust any of these by assigning to the
155    /// corresponding fields after construction.
156    pub fn new(http_bind_addr: impl Into<SocketAddr>) -> Self {
157        Self {
158            http_bind_addr: http_bind_addr.into(),
159            tls: None,
160            limits: Limits::default(),
161            key_cache_capacity: None,
162            access: Arc::new(AllowAll),
163        }
164    }
165}
166
167/// A process-unique identifier for a single relay client connection.
168///
169/// A new id is assigned to every incoming connection when its [`ClientRequest`]
170/// is created, before the access check runs. The same id is passed to
171/// [`AccessControl::on_connect`] and [`AccessControl::on_disconnect`], so an
172/// implementation can match the two callbacks even when one endpoint holds
173/// several concurrent connections.
174#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, derive_more::Display)]
175#[display("{_0}")]
176pub struct ConnectionId(u64);
177
178impl ConnectionId {
179    /// Returns a fresh, process-unique connection id.
180    fn next() -> Self {
181        static NEXT: AtomicU64 = AtomicU64::new(0);
182        Self(NEXT.fetch_add(1, Ordering::Relaxed))
183    }
184}
185
186/// Details about an incoming relay client connection.
187///
188/// Passed to [`AccessControl::on_connect`] to decide whether to admit the connection.
189#[derive(Debug, Clone)]
190pub struct ClientRequest {
191    connection_id: ConnectionId,
192    endpoint_id: EndpointId,
193    protocol_version: ProtocolVersion,
194    request: http::request::Parts,
195}
196
197impl ClientRequest {
198    /// Creates a new [`ClientRequest`] from an [`EndpointId`] and HTTP request parts.
199    ///
200    /// The [`EndpointId`] must be proven by the relay handshake. The request parts
201    /// come from the client's WebSocket request. A fresh [`ConnectionId`] is assigned.
202    pub fn new(
203        endpoint_id: EndpointId,
204        protocol_version: ProtocolVersion,
205        request: http::request::Parts,
206    ) -> Self {
207        Self {
208            connection_id: ConnectionId::next(),
209            endpoint_id,
210            protocol_version,
211            request,
212        }
213    }
214
215    /// Returns the [`ConnectionId`] assigned to this connection.
216    pub fn connection_id(&self) -> ConnectionId {
217        self.connection_id
218    }
219
220    /// Returns the [`ProtocolVersion`] negotiated for this connection.
221    pub fn protocol_version(&self) -> ProtocolVersion {
222        self.protocol_version
223    }
224
225    /// Returns the [`EndpointId`] of the client.
226    ///
227    /// The relay handshake authenticates this id before the access hook
228    /// is invoked. The client proves possession of the secret key for
229    /// this public key by either signing keying material exported from
230    /// the TLS session or a challenge issued by the server.
231    pub fn endpoint_id(&self) -> EndpointId {
232        self.endpoint_id
233    }
234
235    /// Returns the URI of the HTTP request with which the client connected.
236    pub fn uri(&self) -> &http::Uri {
237        &self.request.uri
238    }
239
240    /// Returns an iterator over the query parameters set in the URI of the HTTP request.
241    ///
242    /// Each item is a `(name, value)` pair. Both names and values are percent-decoded.
243    /// The query string is parsed in order, and the same name may appear more than once.
244    pub fn query_pairs(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> {
245        url::form_urlencoded::parse(self.request.uri.query().unwrap_or("").as_bytes())
246    }
247
248    /// Returns the headers of the HTTP request with which the client connected.
249    pub fn headers(&self) -> &http::HeaderMap {
250        &self.request.headers
251    }
252
253    /// Returns the authorization token from the client's HTTP request, if any.
254    ///
255    /// Walks the `Authorization` headers in order and returns the value of
256    /// the first one whose scheme is `Bearer` (matched case-insensitively).
257    /// Headers with a different scheme are skipped.
258    ///
259    /// If none of the `Authorization` headers carries a `Bearer` scheme,
260    /// returns the value of the `token` URL query parameter, or `None` if
261    /// the URL has no `token` parameter.
262    ///
263    /// If an `Authorization` header value is not valid UTF-8 the function returns
264    /// `None` immediately, without checking later headers or the URL query.
265    pub fn auth_token(&self) -> Option<String> {
266        for value in self.request.headers.get_all(AUTHORIZATION) {
267            let value = value.to_str().ok()?;
268            if let Some((scheme, token)) = value.split_once(' ')
269                && scheme.eq_ignore_ascii_case("Bearer")
270            {
271                return Some(token.to_string());
272            }
273        }
274        self.query_pairs()
275            .find(|(name, _)| name == AUTH_TOKEN_URL_QUERY_PARAM)
276            .map(|(_, value)| value.into_owned())
277    }
278}
279
280/// Controls which endpoints may use the relay and observes their lifecycle.
281///
282/// Implement this trait to gate access to a relay server.
283///
284/// Both callbacks carry the connection's [`ConnectionId`], so an implementation
285/// can index connections precisely even when one endpoint holds several.
286pub trait AccessControl: std::fmt::Debug + Send + Sync + 'static {
287    /// Decides whether a connecting client is admitted.
288    ///
289    /// Called once per incoming connection, before the connection is
290    /// registered. Returns [`Access::Allow`] to admit it or [`Access::Deny`]
291    /// to reject it.
292    ///
293    /// Can be implemented as `async fn on_connect(&self, request: &ClientRequest) -> Access`.
294    fn on_connect(&self, request: &ClientRequest) -> impl Future<Output = Access> + Send;
295
296    /// Notifies that a connection has ended.
297    ///
298    /// Called once for every connection that [`Self::on_connect`] admitted,
299    /// identified by the same [`ConnectionId`].
300    ///
301    /// Note that this is a sync method being called in an async context. Make sure that your
302    /// implementation does not block the runtime.
303    fn on_disconnect(&self, endpoint_id: EndpointId, connection_id: ConnectionId) {
304        let _ = (endpoint_id, connection_id);
305    }
306}
307
308/// A dyn-compatible version of [`AccessControl`] that returns boxed futures.
309///
310/// Any type that implements [`AccessControl`] automatically implements
311/// `DynAccessControl`. Wrap it in an `Arc` to store it as an
312/// `Arc<dyn DynAccessControl>`, for example in [`RelayConfig::access`].
313pub trait DynAccessControl: std::fmt::Debug + Send + Sync + 'static {
314    /// See [`AccessControl::on_connect`].
315    fn on_connect<'a>(
316        &'a self,
317        request: &'a ClientRequest,
318    ) -> Pin<Box<dyn Future<Output = Access> + Send + 'a>>;
319
320    /// See [`AccessControl::on_disconnect`].
321    fn on_disconnect(&self, endpoint_id: EndpointId, connection_id: ConnectionId);
322}
323
324impl<T: AccessControl> DynAccessControl for T {
325    fn on_connect<'a>(
326        &'a self,
327        request: &'a ClientRequest,
328    ) -> Pin<Box<dyn Future<Output = Access> + Send + 'a>> {
329        Box::pin(<Self as AccessControl>::on_connect(self, request))
330    }
331
332    fn on_disconnect(&self, endpoint_id: EndpointId, connection_id: ConnectionId) {
333        <Self as AccessControl>::on_disconnect(self, endpoint_id, connection_id)
334    }
335}
336
337/// An [`AccessControl`] that admits every endpoint.
338///
339/// This is the default used by [`RelayConfig::new`].
340#[derive(Debug, Clone, Copy)]
341pub struct AllowAll;
342
343impl AccessControl for AllowAll {
344    async fn on_connect(&self, _request: &ClientRequest) -> Access {
345        Access::Allow
346    }
347}
348
349/// Access restriction for an endpoint.
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub enum Access {
352    /// Access is allowed.
353    Allow,
354    /// Access is denied.
355    Deny {
356        /// Optional reason for denial to send back to the client.
357        reason: Option<String>,
358    },
359}
360
361/// Reports a connection's disconnect to [`AccessControl`] when dropped.
362///
363/// A guard is created the moment [`AccessControl::on_connect`] admits a
364/// connection, and is then held for the connection's entire lifetime. Dropping
365/// it - whether the connection closed cleanly, hit an error, or setup returned
366/// early - calls [`AccessControl::on_disconnect`] exactly once.
367///
368/// Threading the guard through connection setup and into the connection actor
369/// makes it impossible to admit a connection without eventually reporting its
370/// disconnect, even as the surrounding code changes.
371///
372/// Embedders that register connections through [`Clients::register`] construct
373/// the guard themselves; see that method for the expected lifecycle.
374///
375/// [`Clients::register`]: crate::server::clients::Clients::register
376#[derive(Debug)]
377pub struct OnDisconnectGuard {
378    access: Option<Arc<dyn DynAccessControl>>,
379    endpoint_id: EndpointId,
380    connection_id: ConnectionId,
381}
382
383impl OnDisconnectGuard {
384    /// Creates a guard for the connection described by `request`.
385    ///
386    /// Dropping the guard calls [`AccessControl::on_disconnect`] on `access`
387    /// with the request's [`EndpointId`] and [`ConnectionId`]. Create it only
388    /// once [`AccessControl::on_connect`] has admitted the connection.
389    pub fn for_access_control(access: Arc<dyn DynAccessControl>, request: &ClientRequest) -> Self {
390        Self {
391            access: Some(access),
392            endpoint_id: request.endpoint_id(),
393            connection_id: request.connection_id(),
394        }
395    }
396
397    /// Creates a no-op guard for `endpoint_id`.
398    ///
399    /// The guard carries `endpoint_id` and a fresh [`ConnectionId`] but has no
400    /// access control attached, so dropping it does nothing.
401    pub fn empty(endpoint_id: EndpointId) -> Self {
402        Self {
403            access: None,
404            endpoint_id,
405            connection_id: ConnectionId::next(),
406        }
407    }
408
409    /// Returns the [`EndpointId`] of the guarded connection.
410    pub fn endpoint_id(&self) -> EndpointId {
411        self.endpoint_id
412    }
413
414    /// Returns the [`ConnectionId`] of the guarded connection.
415    pub fn connection_id(&self) -> ConnectionId {
416        self.connection_id
417    }
418}
419
420impl Drop for OnDisconnectGuard {
421    fn drop(&mut self) {
422        if let Some(access) = self.access.as_ref() {
423            access.on_disconnect(self.endpoint_id, self.connection_id);
424        }
425    }
426}
427
428/// Configuration for the QUIC server.
429#[derive(Debug)]
430#[non_exhaustive]
431pub struct QuicConfig {
432    /// The socket address on which the QUIC server should bind.
433    ///
434    /// Normally you'd chose port `7842`, see [`crate::defaults::DEFAULT_RELAY_QUIC_PORT`].
435    pub bind_addr: SocketAddr,
436    /// The TLS server configuration for the QUIC server.
437    ///
438    /// If this [`rustls::ServerConfig`] does not support TLS 1.3, the QUIC server will fail
439    /// to spawn.
440    ///
441    /// Will use the TLS config from [`RelayConfig::tls`] if unset. If neither is set the QUIC
442    /// server will fail to spawn.
443    pub server_config: Option<rustls::ServerConfig>,
444}
445
446impl QuicConfig {
447    /// Creates a new [`QuicConfig`] bound to `bind_addr`.
448    ///
449    /// The TLS server config is left unset and inherited from [`RelayConfig::tls`].
450    pub fn new(bind_addr: impl Into<SocketAddr>) -> Self {
451        Self {
452            bind_addr: bind_addr.into(),
453            server_config: None,
454        }
455    }
456}
457
458/// TLS configuration for Relay server.
459///
460/// Normally the Relay server accepts connections on both HTTPS and HTTP.
461#[derive(Debug)]
462#[non_exhaustive]
463pub struct TlsConfig {
464    /// The socket address on which to serve the HTTPS server.
465    ///
466    /// Since the captive portal probe has to run over plain text HTTP and TLS is used for
467    /// the main relay server this has to be on a different port.  When TLS is not enabled
468    /// this is served on the [`RelayConfig::http_bind_addr`] socket address.
469    ///
470    /// Normally you'd choose port `443`.
471    pub https_bind_addr: SocketAddr,
472    /// Mode for getting a cert.
473    pub cert: CertConfig,
474}
475
476impl TlsConfig {
477    /// Creates a new [`TlsConfig`] with the given bind address and certificate configuration.
478    pub fn new(https_bind_addr: impl Into<SocketAddr>, cert: CertConfig) -> Self {
479        Self {
480            https_bind_addr: https_bind_addr.into(),
481            cert,
482        }
483    }
484}
485
486/// Rate limits.
487// TODO: accept_conn_limit and accept_conn_burst are not currently implemented.
488#[derive(Debug, Default)]
489#[non_exhaustive]
490pub struct Limits {
491    /// Rate limits for incoming traffic from a client connection.
492    pub client_rx: Option<ClientRateLimit>,
493    /// Rate limit for accepting new connections. Unlimited if not set.
494    ///
495    /// Not currently implemented, setting this has no effect.
496    pub accept_conn_limit: Option<f64>,
497    /// Burst limit for accepting new connections. Unlimited if not set.
498    ///
499    /// Not currently implemented, setting this has no effect.
500    pub accept_conn_burst: Option<usize>,
501}
502
503/// Per-client rate limit configuration.
504#[derive(Debug, Copy, Clone, PartialEq, Eq)]
505#[non_exhaustive]
506pub struct ClientRateLimit {
507    /// Max number of bytes per second to read from the client connection.
508    pub bytes_per_second: NonZeroU32,
509    /// Max number of bytes to read in a single burst.
510    pub max_burst_bytes: Option<NonZeroU32>,
511}
512
513impl ClientRateLimit {
514    /// Creates a new [`ClientRateLimit`] with the given byte rate.
515    ///
516    /// `max_burst_bytes` is left unset; assign it after construction to allow bursting.
517    pub fn new(bytes_per_second: NonZeroU32) -> Self {
518        Self {
519            bytes_per_second,
520            max_burst_bytes: None,
521        }
522    }
523}
524
525/// TLS certificate configuration.
526#[derive(Debug)]
527#[non_exhaustive]
528pub enum CertConfig {
529    /// Use Let's Encrypt.
530    LetsEncrypt {
531        /// Configuration for the ACME client.
532        acme_config: AcmeConfig,
533        /// Builder for the [`rustls::ServerConfig`].
534        ///
535        /// The ACME resolver will be injected when starting the server.
536        server_config_builder: rustls::ConfigBuilder<rustls::ServerConfig, WantsServerCert>,
537    },
538    /// Use a TLS key and certificate chain that can be reloaded.
539    Manual {
540        /// The [`rustls::ServerConfig`] to use.
541        ///
542        /// This needs to have the certificates or a certificate loader, it will be used by the server as-is.
543        server_config: rustls::ServerConfig,
544    },
545}
546
547/// Configuration for the ACME client.
548#[derive(Debug, Clone)]
549pub struct AcmeConfig {
550    pub(crate) directory_url: String,
551    pub(crate) domains: Vec<String>,
552    pub(crate) contact: Vec<String>,
553    pub(crate) cache_path: Option<PathBuf>,
554    pub(crate) tls_config: CaTlsConfig,
555}
556
557impl AcmeConfig {
558    /// Creates a new [`AcmeConfig`] with a ACME directory URL.
559    pub fn new(directory_url: String) -> Self {
560        Self {
561            directory_url,
562            domains: Vec::new(),
563            contact: Vec::new(),
564            cache_path: None,
565            tls_config: CaTlsConfig::default(),
566        }
567    }
568
569    /// Creates a new [`AcmeConfig`] with the Let's Encrypt directory URL.
570    pub fn letsencrypt(production: bool) -> Self {
571        let url = if production {
572            LETS_ENCRYPT_PRODUCTION_DIRECTORY
573        } else {
574            LETS_ENCRYPT_STAGING_DIRECTORY
575        };
576        Self::new(url.to_string())
577    }
578
579    /// Provides the list of domains for which certificates should be obtained.
580    pub fn domains(mut self, domains: Vec<String>) -> Self {
581        self.domains = domains;
582        self
583    }
584
585    /// Provides a list of contacts for the account.
586    ///
587    /// Note that email addresses must include a `mailto:` prefix.
588    pub fn contact(mut self, contact: Vec<String>) -> Self {
589        self.contact = contact;
590        self
591    }
592
593    /// Sets the directory where to cache certificates.
594    ///
595    /// If not called certificates will not be cached.
596    pub fn cache_path(mut self, path: PathBuf) -> Self {
597        self.cache_path = Some(path);
598        self
599    }
600
601    /// Sets the [`CaTlsConfig`] used to verify the ACME server's TLS certificate.
602    ///
603    /// Defaults to [`CaTlsConfig::embedded`]. Set a config with extra roots when targeting
604    /// an ACME server whose certificate is not signed by a publicly trusted CA, such as a
605    /// local test server.
606    pub fn tls_config(mut self, tls_config: CaTlsConfig) -> Self {
607        self.tls_config = tls_config;
608        self
609    }
610}
611
612/// A running Relay + QAD server.
613///
614/// This is a full Relay server, including QAD, Relay and various associated HTTP services.
615///
616/// Dropping this will stop the server.
617#[derive(Debug)]
618pub struct Server {
619    /// The address of the HTTP server, if configured.
620    http_addr: Option<SocketAddr>,
621    /// The address of the HTTPS server, if the relay server is using TLS.
622    ///
623    /// If the Relay server is not using TLS then it is served from the
624    /// [`Server::http_addr`].
625    https_addr: Option<SocketAddr>,
626    /// The address of the QUIC server, if configured.
627    quic_addr: Option<SocketAddr>,
628    /// Handle to the relay server.
629    relay_handle: Option<http_server::ServerHandle>,
630    /// Handle to the relay service for runtime control.
631    relay_service: Option<http_server::RelayService>,
632    /// Handle to the quic server.
633    quic_handle: Option<QuicServerHandle>,
634    /// The main task running the server.
635    supervisor: AbortOnDropHandle<Result<(), SupervisorError>>,
636    metrics: RelayMetrics,
637    metrics_server: Option<iroh_metrics::service::MetricsServer>,
638}
639
640/// Server spawn errors
641#[allow(missing_docs)]
642#[stack_error(derive, add_meta, std_sources)]
643#[non_exhaustive]
644pub enum SpawnError {
645    #[error("Unable to get local address")]
646    LocalAddr { source: std::io::Error },
647    #[error("Failed to bind QAD listener")]
648    QuicSpawn { source: QuicSpawnError },
649    #[error("Failed to parse TLS header")]
650    TlsHeaderParse { source: InvalidHeaderValue },
651    #[error("Failed to bind TcpListener")]
652    BindTlsListener { source: std::io::Error },
653    #[error("Failed to build ACME client TLS config")]
654    AcmeClientTlsConfig {
655        #[error(std_err)]
656        source: std::io::Error,
657    },
658    #[error("No local address")]
659    NoLocalAddr { source: std::io::Error },
660    #[error("Failed to bind server socket to {addr}")]
661    BindTcpListener {
662        source: std::io::Error,
663        addr: SocketAddr,
664    },
665    #[error("Error starting metrics server")]
666    Metrics {
667        #[error(std_err)]
668        source: std::io::Error,
669    },
670}
671
672/// Server task errors
673#[allow(missing_docs)]
674#[stack_error(derive, add_meta)]
675#[non_exhaustive]
676pub enum SupervisorError {
677    #[error("Acme event stream finished")]
678    AcmeEventStreamFinished {},
679    #[error(transparent)]
680    JoinError {
681        #[error(from, std_err)]
682        source: JoinError,
683    },
684    #[error("No relay services are enabled")]
685    NoRelayServicesEnabled {},
686    #[error("Task cancelled")]
687    TaskCancelled {},
688}
689
690impl Server {
691    /// Starts the server.
692    pub async fn spawn(config: ServerConfig) -> Result<Self, SpawnError> {
693        let mut tasks = JoinSet::new();
694
695        let metrics = RelayMetrics::default();
696
697        #[cfg(not(feature = "metrics"))]
698        let metrics_server = None;
699
700        #[cfg(feature = "metrics")]
701        let metrics_server = if let Some(addr) = config.metrics_addr {
702            debug!("Starting metrics server");
703            let mut registry = iroh_metrics::Registry::default();
704            registry.register_all(&metrics);
705            let server = iroh_metrics::service::MetricsServer::spawn(addr, Arc::new(registry))
706                .await
707                .map_err(|err| e!(SpawnError::Metrics, err))?;
708            Some(server)
709        } else {
710            None
711        };
712
713        let (relay_server, http_addr, tls_config) = match config.relay {
714            Some(relay_config) => {
715                debug!("Starting Relay server");
716                let mut headers = HeaderMap::new();
717                for (name, value) in TLS_HEADERS.iter() {
718                    headers.insert(
719                        *name,
720                        value
721                            .parse()
722                            .map_err(|err| e!(SpawnError::TlsHeaderParse, err))?,
723                    );
724                }
725                let relay_bind_addr = match relay_config.tls {
726                    Some(ref tls) => tls.https_bind_addr,
727                    None => relay_config.http_bind_addr,
728                };
729                let key_cache_capacity = relay_config
730                    .key_cache_capacity
731                    .unwrap_or(DEFAULT_KEY_CACHE_CAPACITY);
732                let mut builder = http_server::ServerBuilder::new(relay_bind_addr)
733                    .metrics(metrics.server.clone())
734                    .headers(headers)
735                    .key_cache_capacity(key_cache_capacity)
736                    .access(relay_config.access)
737                    .request_handler(Method::GET, "/", Box::new(root_handler))
738                    .request_handler(Method::GET, "/index.html", Box::new(root_handler))
739                    .request_handler(Method::GET, RELAY_PROBE_PATH, Box::new(probe_handler))
740                    .request_handler(Method::GET, "/robots.txt", Box::new(robots_handler))
741                    .request_handler(Method::GET, "/healthz", Box::new(healthz_handler));
742                if let Some(cfg) = relay_config.limits.client_rx {
743                    builder = builder.client_rx_ratelimit(cfg);
744                }
745                let (http_addr, tls_config) = match relay_config.tls {
746                    Some(tls_config) => {
747                        let server_tls_config = match tls_config.cert {
748                            CertConfig::LetsEncrypt {
749                                acme_config,
750                                server_config_builder,
751                            } => {
752                                let cache = acme_config.cache_path.map(DirCache::new);
753                                let crypto_provider =
754                                    server_config_builder.crypto_provider().clone();
755                                let client_tls_config = acme_config
756                                    .tls_config
757                                    .client_config(crypto_provider)
758                                    .map_err(|err| e!(SpawnError::AcmeClientTlsConfig, err))?;
759                                let config =
760                                    tokio_rustls_acme::AcmeConfig::new_with_client_tls_config(
761                                        acme_config.domains,
762                                        Arc::new(client_tls_config),
763                                    )
764                                    .contact(acme_config.contact)
765                                    .directory(acme_config.directory_url)
766                                    .cache_option(cache);
767                                let mut state = config.state();
768                                let resolver = state.resolver().clone();
769                                let server_config =
770                                    server_config_builder.with_cert_resolver(resolver);
771                                let acceptor =
772                                    http_server::TlsAcceptor::LetsEncrypt(state.acceptor());
773                                tasks.spawn(
774                                    async move {
775                                        while let Some(event) = state.next().await {
776                                            match event {
777                                                Ok(ok) => debug!("acme event: {ok:?}"),
778                                                Err(err) => error!("error: {err:?}"),
779                                            }
780                                        }
781                                        Err(e!(SupervisorError::AcmeEventStreamFinished))
782                                    }
783                                    .instrument(info_span!("acme")),
784                                );
785                                http_server::TlsConfig {
786                                    config: Arc::new(server_config),
787                                    acceptor,
788                                }
789                            }
790                            CertConfig::Manual { server_config } => {
791                                let server_config = Arc::new(server_config);
792                                let acceptor =
793                                    tokio_rustls::TlsAcceptor::from(server_config.clone());
794                                let acceptor = http_server::TlsAcceptor::Manual(acceptor);
795                                http_server::TlsConfig {
796                                    config: server_config,
797                                    acceptor,
798                                }
799                            }
800                        };
801                        builder = builder.tls_config(Some(server_tls_config.clone()));
802
803                        // Some services always need to be served over HTTP without TLS.  Run
804                        // these standalone.
805                        let http_listener = TcpListener::bind(&relay_config.http_bind_addr)
806                            .await
807                            .map_err(|err| e!(SpawnError::BindTlsListener, err))?;
808                        let http_addr = http_listener
809                            .local_addr()
810                            .map_err(|err| e!(SpawnError::NoLocalAddr, err))?;
811                        tasks.spawn(
812                            async move {
813                                run_captive_portal_service(http_listener).await;
814                                Ok(())
815                            }
816                            .instrument(info_span!("http-service", addr = %http_addr)),
817                        );
818                        (Some(http_addr), Some(server_tls_config))
819                    }
820                    None => {
821                        // If running Relay without TLS add the plain HTTP server directly
822                        // to the Relay server.
823                        builder = builder.request_handler(
824                            Method::GET,
825                            "/generate_204",
826                            Box::new(serve_no_content_handler),
827                        );
828                        (None, None)
829                    }
830                };
831                let relay_server = builder.spawn().await?;
832                (Some(relay_server), http_addr, tls_config)
833            }
834            None => (None, None, None),
835        };
836        // If http_addr is Some then relay_server is serving HTTPS.  If http_addr is None
837        // relay_server is serving HTTP, including the /generate_204 service.
838        let relay_addr = relay_server.as_ref().map(|srv| srv.addr());
839        let relay_handle = relay_server.as_ref().map(|srv| srv.handle());
840        let relay_service = relay_server.as_ref().map(|srv| srv.service().clone());
841
842        let quic_server = match config.quic {
843            Some(quic_config) => {
844                debug!("Starting QUIC server {}", quic_config.bind_addr);
845                let server_config = quic_config
846                    .server_config
847                    .or(tls_config.map(|config| (*config.config).clone()))
848                    .ok_or_else(|| {
849                        e!(SpawnError::QuicSpawn, e!(QuicSpawnError::TlsNotConfigured))
850                    })?;
851                Some(
852                    QuicServer::spawn(quic_config.bind_addr, server_config, metrics.server.clone())
853                        .map_err(|err| e!(SpawnError::QuicSpawn, err))?,
854                )
855            }
856            None => None,
857        };
858        let quic_addr = quic_server.as_ref().map(|srv| srv.bind_addr());
859        let quic_handle = quic_server.as_ref().map(|srv| srv.handle());
860
861        let task = tokio::spawn(relay_supervisor(tasks, relay_server, quic_server));
862
863        Ok(Self {
864            http_addr: http_addr.or(relay_addr),
865            https_addr: http_addr.and(relay_addr),
866            quic_addr,
867            relay_handle,
868            relay_service,
869            quic_handle,
870            supervisor: AbortOnDropHandle::new(task),
871            metrics,
872            metrics_server,
873        })
874    }
875
876    /// Requests graceful shutdown.
877    ///
878    /// Returns once all server tasks have stopped.
879    pub async fn shutdown(self) -> Result<(), SupervisorError> {
880        // Only the Relay server and QUIC server need shutting down, the supervisor will abort the tasks in
881        // the JoinSet when the server terminates.
882        if let Some(handle) = self.relay_handle {
883            handle.shutdown();
884        }
885        if let Some(handle) = self.quic_handle {
886            handle.shutdown();
887        }
888        if let Some(server) = self.metrics_server {
889            server.shutdown().await;
890        }
891        self.supervisor.await?
892    }
893
894    /// Waits for the server's supervisor task to finish.
895    ///
896    /// Returns the exit result of the supervisor task. Unlike [`Self::shutdown`], this does
897    /// *not* request shutdown, it only waits for the server to terminate on its own (for
898    /// example, after an internal error or because the supervisor was aborted from
899    /// elsewhere). The outer [`JoinError`] is only produced if the supervisor task itself
900    /// panics or is aborted.
901    pub async fn join(&mut self) -> Result<Result<(), SupervisorError>, JoinError> {
902        (&mut self.supervisor).await
903    }
904
905    /// The socket address the HTTPS server is listening on.
906    pub fn https_addr(&self) -> Option<SocketAddr> {
907        self.https_addr
908    }
909
910    /// The socket address the HTTP server is listening on.
911    pub fn http_addr(&self) -> Option<SocketAddr> {
912        self.http_addr
913    }
914
915    /// The socket address the QUIC server is listening on.
916    pub fn quic_addr(&self) -> Option<SocketAddr> {
917        self.quic_addr
918    }
919
920    /// Get the server's https [`RelayUrl`].
921    ///
922    /// This uses [`Self::https_addr`] so it's mostly useful for local development.
923    #[cfg(feature = "test-utils")]
924    pub fn https_url(&self) -> Option<RelayUrl> {
925        self.https_addr.map(|addr| {
926            url::Url::parse(&format!("https://{addr}"))
927                .expect("valid url")
928                .into()
929        })
930    }
931
932    /// Get the server's http [`RelayUrl`].
933    ///
934    /// This uses [`Self::http_addr`] so it's mostly useful for local development.
935    #[cfg(feature = "test-utils")]
936    pub fn http_url(&self) -> Option<RelayUrl> {
937        self.http_addr.map(|addr| {
938            url::Url::parse(&format!("http://{addr}"))
939                .expect("valid url")
940                .into()
941        })
942    }
943
944    /// Returns the metrics collected in the relay server.
945    pub fn metrics(&self) -> &RelayMetrics {
946        &self.metrics
947    }
948
949    /// Returns a handle to the embedded [`RelayService`] for runtime control.
950    pub fn relay_service(&self) -> Option<&RelayService> {
951        self.relay_service.as_ref()
952    }
953}
954
955/// Supervisor for the relay server tasks.
956///
957/// As soon as one of the tasks exits, all other tasks are stopped and the server stops.
958/// The supervisor finishes once all tasks are finished.
959#[instrument(skip_all)]
960async fn relay_supervisor(
961    mut tasks: JoinSet<Result<(), SupervisorError>>,
962    mut relay_http_server: Option<http_server::Server>,
963    mut quic_server: Option<QuicServer>,
964) -> Result<(), SupervisorError> {
965    let quic_enabled = quic_server.is_some();
966    let mut quic_fut = match quic_server {
967        Some(ref mut server) => n0_future::Either::Left(server.task_handle()),
968        None => n0_future::Either::Right(n0_future::future::pending()),
969    };
970    let relay_enabled = relay_http_server.is_some();
971    let mut relay_fut = match relay_http_server {
972        Some(ref mut server) => n0_future::Either::Left(server.task_handle()),
973        None => n0_future::Either::Right(n0_future::future::pending()),
974    };
975    let res = tokio::select! {
976        biased;
977        Some(ret) = tasks.join_next() => ret,
978        ret = &mut quic_fut, if quic_enabled => ret.map(Ok),
979        ret = &mut relay_fut, if relay_enabled => ret.map(Ok),
980        else => Ok(Err(e!(SupervisorError::NoRelayServicesEnabled))),
981    };
982    let ret = match res {
983        Ok(Ok(())) => {
984            debug!("Task exited");
985            Ok(())
986        }
987        Ok(Err(err)) => {
988            error!(%err, "Task failed");
989            Err(err)
990        }
991        Err(err) => {
992            if let Ok(panic) = err.try_into_panic() {
993                error!("Task panicked");
994                std::panic::resume_unwind(panic);
995            }
996            debug!("Task cancelled");
997            Err(e!(SupervisorError::TaskCancelled))
998        }
999    };
1000
1001    // Ensure the HTTP server terminated, there is no harm in calling this after it is
1002    // already shut down.
1003    if let Some(server) = relay_http_server {
1004        server.shutdown();
1005    }
1006
1007    // Ensure the QUIC server is closed
1008    if let Some(server) = quic_server {
1009        server.shutdown().await;
1010    }
1011
1012    // Stop all remaining tasks
1013    tasks.shutdown().await;
1014
1015    ret
1016}
1017
1018fn root_handler(
1019    _r: Request<Incoming>,
1020    response: ResponseBuilder,
1021) -> HyperResult<Response<BytesBody>> {
1022    let body: BytesBody = Box::new(Full::from(INDEX));
1023    response
1024        .status(StatusCode::OK)
1025        .header("Content-Type", "text/html; charset=utf-8")
1026        .body(body)
1027        .map_err(|err| Box::new(err) as HyperError)
1028}
1029
1030/// HTTP latency queries
1031fn probe_handler(
1032    _r: Request<Incoming>,
1033    response: ResponseBuilder,
1034) -> HyperResult<Response<BytesBody>> {
1035    response
1036        .status(StatusCode::OK)
1037        .header("Access-Control-Allow-Origin", "*")
1038        .body(body_empty())
1039        .map_err(|err| Box::new(err) as HyperError)
1040}
1041
1042fn robots_handler(
1043    _r: Request<Incoming>,
1044    response: ResponseBuilder,
1045) -> HyperResult<Response<BytesBody>> {
1046    let body: BytesBody = Box::new(Full::from(ROBOTS_TXT));
1047    response
1048        .status(StatusCode::OK)
1049        .body(body)
1050        .map_err(|err| Box::new(err) as HyperError)
1051}
1052
1053/// For captive portal detection.
1054fn serve_no_content_handler<B: hyper::body::Body>(
1055    r: Request<B>,
1056    mut response: ResponseBuilder,
1057) -> HyperResult<Response<BytesBody>> {
1058    let check = |c: &HeaderValue| {
1059        !c.is_empty() && c.len() < 64 && c.as_bytes().iter().all(|c| is_challenge_char(*c as char))
1060    };
1061
1062    if let Some(challenge) = r.headers().get(NO_CONTENT_CHALLENGE_HEADER)
1063        && check(challenge)
1064    {
1065        response = response.header(
1066            NO_CONTENT_RESPONSE_HEADER,
1067            format!("response {}", challenge.to_str()?),
1068        );
1069    }
1070
1071    response
1072        .status(StatusCode::NO_CONTENT)
1073        .body(body_empty())
1074        .map_err(|err| Box::new(err) as HyperError)
1075}
1076
1077fn is_challenge_char(c: char) -> bool {
1078    // Semi-randomly chosen as a limited set of valid characters
1079    c.is_ascii_lowercase()
1080        || c.is_ascii_uppercase()
1081        || c.is_ascii_digit()
1082        || c == '.'
1083        || c == '-'
1084        || c == '_'
1085}
1086
1087/// Health check response
1088#[derive(Serialize)]
1089struct Health {
1090    status: &'static str,
1091    version: &'static str,
1092    git_hash: &'static str,
1093}
1094
1095fn healthz_handler(
1096    _r: Request<Incoming>,
1097    response: ResponseBuilder,
1098) -> HyperResult<Response<BytesBody>> {
1099    let health = Health {
1100        status: "ok",
1101        version: env!("CARGO_PKG_VERSION"),
1102        git_hash: "unknown",
1103    };
1104    let body = serde_json::to_string(&health).unwrap_or_else(|_| r#"{"status":"error"}"#.into());
1105    let body: BytesBody = Box::new(Full::from(body));
1106    response
1107        .status(StatusCode::OK)
1108        .header("Content-Type", "application/json")
1109        .body(body)
1110        .map_err(|err| Box::new(err) as HyperError)
1111}
1112
1113/// This is a future that never returns, drop it to cancel/abort.
1114async fn run_captive_portal_service(http_listener: TcpListener) {
1115    info!("serving");
1116
1117    // If this future is cancelled, this is dropped and all tasks are aborted.
1118    let mut tasks = JoinSet::new();
1119
1120    loop {
1121        tokio::select! {
1122            biased;
1123
1124            Some(res) = tasks.join_next() => {
1125                if let Err(err) = res
1126                    && err.is_panic()
1127                {
1128                    panic!("task panicked: {err:#?}");
1129                }
1130            }
1131
1132            res = http_listener.accept() => {
1133                match res {
1134                    Ok((stream, peer_addr)) => {
1135                        debug!(%peer_addr, "Connection opened",);
1136                        let handler = CaptivePortalService;
1137
1138                        let stream = crate::server::streams::MaybeTlsStream::Plain(stream);
1139                        let stream = hyper_util::rt::TokioIo::new(stream);
1140                        let connection = hyper::server::conn::http1::Builder::new()
1141                            .keep_alive(false)
1142                            .serve_connection(stream, handler);
1143                        tasks.spawn(async move {
1144                            match tokio::time::timeout(Duration::from_secs(30), connection).await {
1145                                Ok(Ok(())) => {}
1146                                Ok(Err(err)) => error!("Failed to serve connection: {err:?}"),
1147                                Err(_) => debug!(%peer_addr, "Captive portal connection timed out"),
1148                            }
1149                        });
1150                    }
1151                    Err(err) => {
1152                        error!(
1153                            "[CaptivePortalService] failed to accept connection: {:#?}",
1154                            err
1155                        );
1156                    }
1157                }
1158            }
1159        }
1160    }
1161}
1162
1163#[derive(Clone)]
1164struct CaptivePortalService;
1165
1166impl hyper::service::Service<Request<Incoming>> for CaptivePortalService {
1167    type Response = Response<BytesBody>;
1168    type Error = HyperError;
1169    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
1170
1171    fn call(&self, req: Request<Incoming>) -> Self::Future {
1172        match (req.method(), req.uri().path()) {
1173            // Captive Portal checker
1174            (&Method::GET, "/generate_204") => {
1175                Box::pin(async move { serve_no_content_handler(req, Response::builder()) })
1176            }
1177            _ => {
1178                // Return 404 not found response.
1179                let body: BytesBody = Box::new(Full::from(NOTFOUND));
1180                let r = Response::builder()
1181                    .status(StatusCode::NOT_FOUND)
1182                    .body(body)
1183                    .map_err(|err| Box::new(err) as HyperError);
1184                Box::pin(async move { r })
1185            }
1186        }
1187    }
1188}
1189
1190#[cfg(test)]
1191mod tests {
1192    use std::{net::Ipv4Addr, sync::Arc, time::Duration};
1193
1194    use http::StatusCode;
1195    use iroh_base::{EndpointId, RelayUrl, SecretKey};
1196    use iroh_dns::dns::DnsResolver;
1197    use n0_error::{Result, StackResultExt, StdResultExt};
1198    use n0_future::{SinkExt, StreamExt};
1199    use n0_tracing_test::traced_test;
1200    use rand::{RngExt, SeedableRng};
1201    use tracing::{info, instrument};
1202    use url::Url;
1203
1204    use super::{
1205        Access, AccessControl, ClientRequest, NO_CONTENT_CHALLENGE_HEADER,
1206        NO_CONTENT_RESPONSE_HEADER, RelayConfig, Server, ServerConfig, SpawnError,
1207    };
1208    use crate::{
1209        client::{ClientBuilder, ConnectError},
1210        protos::{
1211            handshake,
1212            relay::{ClientToRelayMsg, Datagrams, RelayToClientMsg},
1213        },
1214        test_utils::static_resolver,
1215        tls::{self, CaTlsConfig, default_provider},
1216    };
1217
1218    /// An [`AccessControl`] backed by a closure, for tests.
1219    #[derive(derive_more::Debug)]
1220    struct TestAccess(#[debug("access fn")] Box<dyn Fn(&ClientRequest) -> Access + Send + Sync>);
1221
1222    impl AccessControl for TestAccess {
1223        async fn on_connect(&self, request: &ClientRequest) -> Access {
1224            (self.0)(request)
1225        }
1226    }
1227
1228    async fn spawn_local_relay() -> std::result::Result<Server, SpawnError> {
1229        let mut relay = RelayConfig::new((Ipv4Addr::LOCALHOST, 0));
1230        relay.key_cache_capacity = Some(1024);
1231        Server::spawn(ServerConfig {
1232            relay: Some(relay),
1233            quic: None,
1234            metrics_addr: None,
1235        })
1236        .await
1237    }
1238
1239    #[instrument]
1240    async fn try_send_recv(
1241        client_a: &mut crate::client::Client,
1242        client_b: &mut crate::client::Client,
1243        b_key: EndpointId,
1244        msg: Datagrams,
1245    ) -> Result<RelayToClientMsg> {
1246        // try resend 10 times
1247        for _ in 0..10 {
1248            client_a
1249                .send(ClientToRelayMsg::Datagrams {
1250                    dst_endpoint_id: b_key,
1251                    datagrams: msg.clone(),
1252                })
1253                .await?;
1254            let Ok(res) = tokio::time::timeout(Duration::from_millis(500), client_b.next()).await
1255            else {
1256                continue;
1257            };
1258            let res = res.expect("stream finished")?;
1259            return Ok(res);
1260        }
1261        panic!("failed to send and recv message");
1262    }
1263
1264    fn dns_resolver() -> DnsResolver {
1265        DnsResolver::new()
1266    }
1267
1268    fn ring_config() -> rustls::ClientConfig {
1269        rustls::ClientConfig::builder_with_provider(Arc::new(
1270            rustls::crypto::ring::default_provider(),
1271        ))
1272        .with_safe_default_protocol_versions()
1273        .unwrap()
1274        .with_root_certificates(rustls::RootCertStore::empty())
1275        .with_no_client_auth()
1276    }
1277
1278    #[tokio::test]
1279    #[traced_test]
1280    async fn test_no_services() {
1281        let mut server = Server::spawn(ServerConfig::default()).await.unwrap();
1282        let res = tokio::time::timeout(Duration::from_secs(5), server.join())
1283            .await
1284            .expect("timeout, server not finished")
1285            .expect("server task JoinError");
1286        assert!(res.is_err());
1287    }
1288
1289    #[tokio::test]
1290    #[traced_test]
1291    async fn test_conflicting_bind() {
1292        let mut relay = RelayConfig::new((Ipv4Addr::LOCALHOST, 1234));
1293        relay.key_cache_capacity = Some(1024);
1294        let res = Server::spawn(ServerConfig {
1295            relay: Some(relay),
1296            quic: None,
1297            metrics_addr: Some((Ipv4Addr::LOCALHOST, 1234).into()),
1298        })
1299        .await;
1300        assert!(res.is_err()); // AddrInUse
1301    }
1302
1303    #[tokio::test]
1304    #[traced_test]
1305    async fn test_root_handler() {
1306        let server = spawn_local_relay().await.unwrap();
1307        let url = format!("http://{}", server.http_addr().unwrap());
1308
1309        let client = reqwest::Client::builder()
1310            .use_preconfigured_tls(ring_config())
1311            .build()
1312            .unwrap();
1313        let response = client.get(&url).send().await.unwrap();
1314        assert_eq!(response.status(), 200);
1315        let body = response.text().await.unwrap();
1316        assert!(body.contains("iroh.computer"));
1317    }
1318
1319    #[tokio::test]
1320    #[traced_test]
1321    async fn test_captive_portal_service() {
1322        let server = spawn_local_relay().await.unwrap();
1323        let url = format!("http://{}/generate_204", server.http_addr().unwrap());
1324        let challenge = "123az__.";
1325
1326        let client = reqwest::Client::builder()
1327            .use_preconfigured_tls(ring_config())
1328            .build()
1329            .unwrap();
1330        let response = client
1331            .get(&url)
1332            .header(NO_CONTENT_CHALLENGE_HEADER, challenge)
1333            .send()
1334            .await
1335            .unwrap();
1336        assert_eq!(response.status(), StatusCode::NO_CONTENT);
1337        let header = response.headers().get(NO_CONTENT_RESPONSE_HEADER).unwrap();
1338        assert_eq!(header.to_str().unwrap(), format!("response {challenge}"));
1339        let body = response.text().await.unwrap();
1340        assert!(body.is_empty());
1341    }
1342
1343    #[tokio::test]
1344    #[traced_test]
1345    async fn test_relay_clients() -> Result<()> {
1346        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
1347        let server = spawn_local_relay().await?;
1348
1349        let relay_url = format!("http://{}", server.http_addr().unwrap());
1350        let relay_url: RelayUrl = relay_url.parse()?;
1351
1352        let client_config = CaTlsConfig::default()
1353            .client_config(default_provider())
1354            .unwrap();
1355
1356        // set up client a
1357        let a_secret_key = SecretKey::from_bytes(&rng.random());
1358        let a_key = a_secret_key.public();
1359        let resolver = dns_resolver();
1360        info!("client a build & connect");
1361        let mut client_a = ClientBuilder::new(relay_url.clone(), a_secret_key, resolver.clone())
1362            .tls_client_config(client_config.clone())
1363            .connect()
1364            .await?;
1365
1366        // set up client b
1367        let b_secret_key = SecretKey::from_bytes(&rng.random());
1368        let b_key = b_secret_key.public();
1369        info!("client b build & connect");
1370        let mut client_b = ClientBuilder::new(relay_url.clone(), b_secret_key, resolver.clone())
1371            .tls_client_config(client_config)
1372            .connect()
1373            .await?;
1374
1375        info!("sending a -> b");
1376
1377        // send message from a to b
1378        let msg = Datagrams::from("hello, b");
1379        let res = try_send_recv(&mut client_a, &mut client_b, b_key, msg.clone()).await?;
1380        let RelayToClientMsg::Datagrams {
1381            remote_endpoint_id,
1382            datagrams,
1383        } = res
1384        else {
1385            panic!("client_b received unexpected message {res:?}");
1386        };
1387
1388        assert_eq!(a_key, remote_endpoint_id);
1389        assert_eq!(msg, datagrams);
1390
1391        info!("sending b -> a");
1392        // send message from b to a
1393        let msg = Datagrams::from("howdy, a");
1394        let res = try_send_recv(&mut client_b, &mut client_a, a_key, msg.clone()).await?;
1395
1396        let RelayToClientMsg::Datagrams {
1397            remote_endpoint_id,
1398            datagrams,
1399        } = res
1400        else {
1401            panic!("client_a received unexpected message {res:?}");
1402        };
1403
1404        assert_eq!(b_key, remote_endpoint_id);
1405        assert_eq!(msg, datagrams);
1406
1407        Ok(())
1408    }
1409
1410    #[tokio::test]
1411    #[traced_test]
1412    async fn test_relay_access_control() -> Result<()> {
1413        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
1414        let current_span = tracing::info_span!("this is a test");
1415        let _guard = current_span.enter();
1416
1417        let client_config = CaTlsConfig::default()
1418            .client_config(default_provider())
1419            .unwrap();
1420
1421        let a_secret_key = SecretKey::from_bytes(&rng.random());
1422        let a_key = a_secret_key.public();
1423
1424        let mut relay = RelayConfig::new((Ipv4Addr::LOCALHOST, 0));
1425        relay.key_cache_capacity = Some(1024);
1426        relay.access = Arc::new(TestAccess(Box::new(move |request| {
1427            let endpoint_id = request.endpoint_id();
1428            info!("checking {}", endpoint_id);
1429            // reject endpoint a
1430            if endpoint_id == a_key {
1431                Access::Deny { reason: None }
1432            } else {
1433                Access::Allow
1434            }
1435        })));
1436        let server = Server::spawn(ServerConfig {
1437            relay: Some(relay),
1438            quic: None,
1439            metrics_addr: None,
1440        })
1441        .await?;
1442
1443        let relay_url = format!("http://{}", server.http_addr().unwrap());
1444        let relay_url: RelayUrl = relay_url.parse()?;
1445
1446        // set up client a
1447        let resolver = dns_resolver();
1448        let result = ClientBuilder::new(relay_url.clone(), a_secret_key, resolver)
1449            .tls_client_config(client_config.clone())
1450            .connect()
1451            .await;
1452
1453        assert!(
1454            matches!(result, Err(ConnectError::Handshake { source: handshake::Error::ServerDeniedAuth { reason, .. }, .. }) if reason == "not authorized")
1455        );
1456
1457        // test that another client has access
1458
1459        // set up client b
1460        let b_secret_key = SecretKey::from_bytes(&rng.random());
1461        let b_key = b_secret_key.public();
1462
1463        let resolver = dns_resolver();
1464        let mut client_b = ClientBuilder::new(relay_url.clone(), b_secret_key, resolver)
1465            .tls_client_config(client_config.clone())
1466            .connect()
1467            .await?;
1468
1469        // set up client c
1470        let c_secret_key = SecretKey::from_bytes(&rng.random());
1471        let c_key = c_secret_key.public();
1472
1473        let resolver = dns_resolver();
1474        let mut client_c = ClientBuilder::new(relay_url.clone(), c_secret_key, resolver)
1475            .tls_client_config(client_config)
1476            .connect()
1477            .await?;
1478
1479        // send message from b to c
1480        let msg = Datagrams::from("hello, c");
1481        let res = try_send_recv(&mut client_b, &mut client_c, c_key, msg.clone()).await?;
1482
1483        if let RelayToClientMsg::Datagrams {
1484            remote_endpoint_id,
1485            datagrams,
1486        } = res
1487        {
1488            assert_eq!(b_key, remote_endpoint_id);
1489            assert_eq!(msg, datagrams);
1490        } else {
1491            panic!("client_c received unexpected message {res:?}");
1492        }
1493
1494        Ok(())
1495    }
1496
1497    /// Verifies that [`ClientBuilder::auth_token`] forwards a token to the
1498    /// relay so the [`AccessControl::on_connect`] hook can read it via
1499    /// [`ClientRequest::auth_token`].
1500    #[tokio::test]
1501    #[traced_test]
1502    async fn test_relay_client_auth_token_forwarded() -> Result<()> {
1503        const TOKEN: &str = "secret-token";
1504
1505        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
1506        let client_config = CaTlsConfig::default()
1507            .client_config(default_provider())
1508            .unwrap();
1509
1510        let mut relay = RelayConfig::new((Ipv4Addr::LOCALHOST, 0));
1511        relay.key_cache_capacity = Some(1024);
1512        relay.access = Arc::new(TestAccess(Box::new(move |request| {
1513            if request.auth_token().as_deref() == Some(TOKEN) {
1514                Access::Allow
1515            } else {
1516                Access::Deny { reason: None }
1517            }
1518        })));
1519        let server = Server::spawn(ServerConfig {
1520            relay: Some(relay),
1521            quic: None,
1522            metrics_addr: None,
1523        })
1524        .await?;
1525
1526        let relay_url = format!("http://{}", server.http_addr().unwrap());
1527        let relay_url: RelayUrl = relay_url.parse()?;
1528
1529        // No query param: denied.
1530        let secret_key = SecretKey::from_bytes(&rng.random());
1531        let result = ClientBuilder::new(relay_url.clone(), secret_key, dns_resolver())
1532            .tls_client_config(client_config.clone())
1533            .connect()
1534            .await;
1535        assert!(matches!(
1536            result,
1537            Err(ConnectError::Handshake { source: handshake::Error::ServerDeniedAuth { reason, .. }, .. })
1538                if reason == "not authorized"
1539        ));
1540
1541        // Wrong token: denied.
1542        let secret_key = SecretKey::from_bytes(&rng.random());
1543        let result = ClientBuilder::new(relay_url.clone(), secret_key, dns_resolver())
1544            .tls_client_config(client_config.clone())
1545            .auth_token("wrong-token")
1546            .connect()
1547            .await;
1548        assert!(matches!(
1549            result,
1550            Err(ConnectError::Handshake { source: handshake::Error::ServerDeniedAuth { reason, .. }, .. })
1551                if reason == "not authorized"
1552        ));
1553
1554        // Correct token: connection succeeds.
1555        let secret_key = SecretKey::from_bytes(&rng.random());
1556        let _client = ClientBuilder::new(relay_url, secret_key, dns_resolver())
1557            .tls_client_config(client_config)
1558            .auth_token(TOKEN)
1559            .connect()
1560            .await?;
1561
1562        Ok(())
1563    }
1564
1565    #[tokio::test]
1566    #[traced_test]
1567    async fn test_relay_clients_full() -> Result<()> {
1568        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
1569        let server = spawn_local_relay().await.unwrap();
1570        let relay_url = format!("http://{}", server.http_addr().unwrap());
1571        let relay_url: RelayUrl = relay_url.parse().unwrap();
1572
1573        let client_config = CaTlsConfig::default()
1574            .client_config(default_provider())
1575            .unwrap();
1576
1577        // set up client a
1578        let a_secret_key = SecretKey::from_bytes(&rng.random());
1579        let resolver = dns_resolver();
1580        let mut client_a = ClientBuilder::new(relay_url.clone(), a_secret_key, resolver.clone())
1581            .tls_client_config(client_config.clone())
1582            .connect()
1583            .await?;
1584
1585        // set up client b
1586        let b_secret_key = SecretKey::from_bytes(&rng.random());
1587        let b_key = b_secret_key.public();
1588        let _client_b = ClientBuilder::new(relay_url.clone(), b_secret_key, resolver.clone())
1589            .tls_client_config(client_config)
1590            .connect()
1591            .await?;
1592
1593        // send messages from a to b, without b receiving anything.
1594        // we should still keep succeeding to send, even if the packet won't be forwarded
1595        // by the relay server because the server's send queue for b fills up.
1596        let msg = Datagrams::from("hello, b");
1597        for _i in 0..1000 {
1598            client_a
1599                .send(ClientToRelayMsg::Datagrams {
1600                    dst_endpoint_id: b_key,
1601                    datagrams: msg.clone(),
1602                })
1603                .await?;
1604        }
1605        Ok(())
1606    }
1607
1608    /// Regression test: A relay client that prefers IPv6 falls back to IPv4
1609    /// when the advertised IPv6 address is unreachable.
1610    #[tokio::test]
1611    #[traced_test]
1612    async fn test_relay_client_falls_back_to_ipv4() -> Result {
1613        // A relay reachable only over IPv4.
1614        let config = ServerConfig {
1615            relay: Some(RelayConfig::new((Ipv4Addr::LOCALHOST, 0))),
1616            ..Default::default()
1617        };
1618        let server = Server::spawn(config).await?;
1619        let addr = server.http_addr().expect("http relay address");
1620
1621        // Resolves to both the real IPv4 address and an unreachable IPv6 address.
1622        let resolver = static_resolver(
1623            vec![Ipv4Addr::LOCALHOST],
1624            vec!["2001:db8::dead".parse().expect("valid IPv6")],
1625        );
1626        let url: Url = format!("http://relay.test:{}", addr.port())
1627            .parse()
1628            .expect("valid relay url");
1629
1630        let client = ClientBuilder::new(url, SecretKey::generate(), resolver)
1631            .tls_client_config(tls::make_dangerous_client_config())
1632            // Force IPv6 preference
1633            .address_family_selector(|| true);
1634
1635        tokio::time::timeout(Duration::from_secs(10), client.connect())
1636            .await
1637            .with_std_context(|_| "relay connect timed out")?
1638            .context("relay connect")?;
1639
1640        server.shutdown().await.context("relay server shutdown")?;
1641        Ok(())
1642    }
1643}