1use std::{
4 net::{IpAddr, Ipv6Addr, SocketAddr},
5 path::PathBuf,
6 time::{Duration, Instant},
7};
8
9use axum::{
10 Json, Router,
11 extract::{ConnectInfo, Request, State},
12 handler::Handler,
13 http::Method,
14 middleware::{self, Next},
15 response::IntoResponse,
16 routing::get,
17};
18use n0_error::{Result, StdResultExt, anyerr, bail_any};
19use serde::{Deserialize, Serialize};
20use socket2::{SockRef, TcpKeepalive};
21use tokio::{net::TcpListener, task::JoinSet};
22use tower_http::{
23 cors::{self, CorsLayer},
24 trace::TraceLayer,
25};
26use tracing::{Level, info, span, warn};
27
28mod doh;
29mod error;
30mod pkarr;
31mod rate_limiting;
32mod tls;
33
34pub use self::{rate_limiting::RateLimitConfig, tls::CertMode};
35use crate::state::AppState;
36
37const TCP_KEEPALIVE_TIME: Duration = Duration::from_mins(1);
39
40const TCP_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(15);
42
43fn set_keepalive(listener: &std::net::TcpListener) -> std::io::Result<()> {
49 let keepalive = TcpKeepalive::new()
50 .with_time(TCP_KEEPALIVE_TIME)
51 .with_interval(TCP_KEEPALIVE_INTERVAL);
52 SockRef::from(listener).set_tcp_keepalive(&keepalive)
53}
54
55#[derive(Debug, Serialize, Deserialize, Clone)]
57#[non_exhaustive]
58pub struct HttpConfig {
59 pub port: u16,
61 pub bind_addr: Option<IpAddr>,
63}
64
65#[derive(Debug, Serialize, Deserialize, Clone)]
69#[non_exhaustive]
70pub struct HttpsConfig {
71 pub port: u16,
73 pub bind_addr: Option<IpAddr>,
75 pub domains: Vec<String>,
77 pub cert_mode: CertMode,
79 pub letsencrypt_contact: Option<String>,
84 pub letsencrypt_prod: Option<bool>,
89}
90
91#[derive(Debug)]
93pub(crate) struct HttpServer {
94 tasks: JoinSet<std::io::Result<()>>,
95 http_addr: Option<SocketAddr>,
96 https_addr: Option<SocketAddr>,
97}
98
99impl HttpServer {
100 pub(crate) async fn spawn(
102 http_config: Option<HttpConfig>,
103 https_config: Option<HttpsConfig>,
104 rate_limit_config: RateLimitConfig,
105 state: AppState,
106 cert_cache_dir: PathBuf,
107 ) -> Result<HttpServer> {
108 if http_config.is_none() && https_config.is_none() {
109 bail_any!("Either http or https config is required");
110 }
111
112 let app = create_app(state, &rate_limit_config);
113
114 let mut tasks = JoinSet::new();
115
116 let http_addr = if let Some(config) = http_config {
118 let bind_addr = SocketAddr::new(
119 config.bind_addr.unwrap_or(Ipv6Addr::UNSPECIFIED.into()),
120 config.port,
121 );
122 let app = app.clone();
123 let listener = TcpListener::bind(bind_addr)
124 .await
125 .anyerr()?
126 .into_std()
127 .anyerr()?;
128 let bound_addr = listener.local_addr().anyerr()?;
129 set_keepalive(&listener).anyerr()?;
130 let fut = axum_server::from_tcp(listener)?
131 .serve(app.into_make_service_with_connect_info::<SocketAddr>());
132 info!("HTTP server listening on {bind_addr}");
133 tasks.spawn(fut);
134 Some(bound_addr)
135 } else {
136 None
137 };
138
139 let https_addr = if let Some(config) = https_config {
141 let bind_addr = SocketAddr::new(
142 config.bind_addr.unwrap_or(Ipv6Addr::UNSPECIFIED.into()),
143 config.port,
144 );
145 let acceptor = {
146 tokio::fs::create_dir_all(&cert_cache_dir)
147 .await
148 .with_std_context(|_| {
149 format!(
150 "failed to create cert cache dir at {}",
151 cert_cache_dir.display()
152 )
153 })?;
154 config
155 .cert_mode
156 .build(
157 config.domains,
158 cert_cache_dir,
159 config.letsencrypt_contact,
160 config.letsencrypt_prod.unwrap_or(false),
161 )
162 .await?
163 };
164 let listener = TcpListener::bind(bind_addr)
165 .await
166 .anyerr()?
167 .into_std()
168 .anyerr()?;
169 let bound_addr = listener.local_addr().anyerr()?;
170 set_keepalive(&listener).anyerr()?;
171 let fut = axum_server::from_tcp(listener)?
172 .acceptor(acceptor)
173 .serve(app.into_make_service_with_connect_info::<SocketAddr>());
174 info!("HTTPS server listening on {bind_addr}");
175 tasks.spawn(fut);
176 Some(bound_addr)
177 } else {
178 None
179 };
180
181 Ok(HttpServer {
182 tasks,
183 http_addr,
184 https_addr,
185 })
186 }
187
188 pub(crate) fn http_addr(&self) -> Option<SocketAddr> {
190 self.http_addr
191 }
192
193 pub(crate) fn https_addr(&self) -> Option<SocketAddr> {
195 self.https_addr
196 }
197
198 pub(crate) async fn shutdown(mut self) -> Result<()> {
200 self.tasks.abort_all();
202 self.run_until_done().await?;
203 Ok(())
204 }
205
206 pub(crate) async fn run_until_done(mut self) -> Result<()> {
210 let mut final_res: Result<()> = Ok(());
211 while let Some(res) = self.tasks.join_next().await {
212 match res {
213 Ok(Ok(())) => {}
214 Err(err) if err.is_cancelled() => {}
215 Ok(Err(err)) => {
216 warn!(?err, "task failed");
217 final_res = Err(anyerr!(err, "task"));
218 }
219 Err(err) => {
220 warn!(?err, "task panicked");
221 final_res = Err(anyerr!(err, "join"));
222 }
223 }
224 }
225 final_res
226 }
227}
228
229#[derive(Serialize)]
231struct Health {
232 status: &'static str,
233 version: &'static str,
234 git_hash: &'static str,
235}
236
237async fn healthz() -> Json<Health> {
238 Json(Health {
239 status: "ok",
240 version: env!("CARGO_PKG_VERSION"),
241 git_hash: "unknown",
242 })
243}
244
245pub(crate) fn create_app(state: AppState, rate_limit_config: &RateLimitConfig) -> Router {
246 let cors = CorsLayer::new()
248 .allow_methods([Method::GET, Method::POST, Method::PUT])
250 .allow_origin(cors::Any);
252
253 let trace = TraceLayer::new_for_http().make_span_with(|request: &http::Request<_>| {
255 let conn_info = request
256 .extensions()
257 .get::<ConnectInfo<SocketAddr>>()
258 .expect("connectinfo extension to be present");
259 let span = span!(
260 Level::DEBUG,
261 "http_request",
262 method = ?request.method(),
263 uri = ?request.uri(),
264 src = %conn_info.0,
265 );
266 span
267 });
268
269 let rate_limit = rate_limiting::create(rate_limit_config);
271
272 let router = Router::new()
276 .route("/dns-query", get(doh::get).post(doh::post))
277 .route(
278 "/pkarr/{key}",
279 if let Some(rate_limit) = rate_limit {
280 get(pkarr::get).put(pkarr::put.layer(rate_limit))
281 } else {
282 get(pkarr::get).put(pkarr::put)
283 },
284 )
285 .route("/healthcheck", get(|| async { "OK" }))
287 .route("/healthz", get(healthz))
288 .route("/", get(|| async { "Hi!" }))
289 .with_state(state.clone());
290
291 router
293 .layer(cors)
294 .layer(trace)
295 .route_layer(middleware::from_fn_with_state(state, metrics_middleware))
296}
297
298async fn metrics_middleware(
307 State(state): State<AppState>,
308 req: Request,
309 next: Next,
310) -> impl IntoResponse {
311 let start = Instant::now();
312 let response = next.run(req).await;
313 let latency = start.elapsed().as_millis();
314 let status = response.status();
315 state
316 .metrics
317 .http_requests_duration_ms
318 .inc_by(latency as u64);
319 state.metrics.http_requests.inc();
320 if status.is_success() {
321 state.metrics.http_requests_success.inc();
322 } else {
323 state.metrics.http_requests_error.inc();
324 }
325 response
326}
327
328#[cfg(test)]
329mod tests {
330 use std::{
331 net::{IpAddr, Ipv4Addr},
332 sync::Arc,
333 };
334
335 use hickory_resolver::{
336 config::{NameServerConfig, ResolverConfig},
337 net::runtime::TokioRuntimeProvider,
338 };
339 use hickory_server::proto::rr::RecordType;
340 use iroh::{
341 RelayUrl, SecretKey,
342 address_lookup::{EndpointInfo, PkarrRelayClient},
343 dns::DnsResolver,
344 tls::{CaTlsConfig, default_provider},
345 };
346 use n0_error::StdResultExt;
347 use n0_tracing_test::traced_test;
348 use rand::{RngExt, SeedableRng};
349
350 use crate::{http::HttpsConfig, server::Server};
351
352 #[tokio::test]
353 #[traced_test]
354 async fn test_doh() -> n0_error::Result {
355 let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(0);
356 let dir = tempfile::tempdir()?;
357 let https_config = HttpsConfig {
358 port: 0,
359 bind_addr: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
360 domains: vec!["localhost".to_string()],
361 cert_mode: crate::http::CertMode::SelfSigned,
362 letsencrypt_contact: None,
363 letsencrypt_prod: None,
364 };
365 let server =
366 Server::spawn_for_tests_with_options(dir.path(), None, None, Some(https_config))
367 .await?;
368
369 const RELAY_URL: &str = "https://relay.example./";
370 let (name_z32, signed_packet) = {
371 let secret_key = SecretKey::from_bytes(&rng.random());
372 let endpoint_id = secret_key.public();
373 let relay_url: RelayUrl = RELAY_URL.parse().expect("valid url");
374 let endpoint_info = EndpointInfo::new(endpoint_id).with_relay_url(relay_url.clone());
375 (
376 secret_key.public().to_z32(),
377 endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?,
378 )
379 };
380
381 let http_url = server.http_url().expect("http is bound");
382 let tls_config = CaTlsConfig::default()
383 .client_config(default_provider())
384 .expect("infallible");
385 let pkarr = PkarrRelayClient::new(
386 format!("{http_url}pkarr").parse().anyerr()?,
387 tls_config,
388 DnsResolver::default(),
389 );
390 pkarr.publish(&signed_packet).await?;
391
392 let client = reqwest::Client::builder()
394 .http2_prior_knowledge()
395 .use_preconfigured_tls(self::tls::insecure_tls_config())
396 .build()
397 .anyerr()?;
398
399 let url = format!(
401 "{http_url}dns-query?name={}&type=txt",
402 format_args!("_iroh.{name_z32}."),
403 );
404 let res = client
405 .get(url)
406 .header("accept", "application/dns-json")
407 .send()
408 .await
409 .anyerr()?
410 .json::<super::doh::response::DnsResponse>()
411 .await
412 .anyerr()?;
413 assert_eq!(res.answer.len(), 1);
414 assert_eq!(res.answer[0].name, format!("_iroh.{name_z32}."));
415 assert_eq!(res.answer[0].data, format!("relay={RELAY_URL}"));
416
417 let https_url = server.https_url().expect("https is bound");
419 let url = format!(
420 "{https_url}dns-query?name={}&type=txt",
421 format_args!("_iroh.{name_z32}."),
422 );
423 let res = client
424 .get(url)
425 .header("accept", "application/dns-json")
426 .send()
427 .await
428 .anyerr()?
429 .json::<super::doh::response::DnsResponse>()
430 .await
431 .anyerr()?;
432 assert_eq!(res.answer.len(), 1);
433 assert_eq!(res.answer[0].name, format!("_iroh.{name_z32}."));
434 assert_eq!(res.answer[0].data, format!("relay={RELAY_URL}"));
435
436 let client = {
438 let https_addr = server.https_addr().expect("https is bound");
439 let mut name_server =
440 NameServerConfig::https(https_addr.ip(), Arc::from("localhost"), None);
441 for connection in &mut name_server.connections {
442 connection.port = https_addr.port();
443 }
444 let config = ResolverConfig::from_parts(None, vec![], vec![name_server]);
445
446 hickory_resolver::Resolver::builder_with_config(config, TokioRuntimeProvider::default())
447 .with_tls_config(self::tls::insecure_tls_config())
448 .build()
449 .anyerr()?
450 };
451
452 let res = client
453 .txt_lookup(format!("_iroh.{name_z32}."))
454 .await
455 .anyerr()?;
456 let records = res.answers();
457 assert_eq!(records.len(), 1);
458 assert_eq!(records[0].record_type(), RecordType::TXT);
459 let txt_data = match &records[0].data {
460 hickory_server::proto::rr::RData::TXT(txt) => &txt.txt_data,
461 other => panic!("expected TXT record, got {other:?}"),
462 };
463 assert_eq!(&txt_data[0][..], format!("relay={RELAY_URL}").as_bytes());
464
465 server.shutdown().await?;
466 Ok(())
467 }
468
469 mod tls {
470 use std::sync::Arc;
471
472 use rustls::{
473 DigitallySignedStruct, RootCertStore,
474 client::{
475 ClientConfig,
476 danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
477 },
478 crypto::{
479 CryptoProvider, ring::default_provider, verify_tls12_signature,
480 verify_tls13_signature,
481 },
482 pki_types::{CertificateDer, ServerName, UnixTime},
483 };
484
485 #[derive(Debug)]
486 struct NoCertificateVerification(CryptoProvider);
487
488 impl Default for NoCertificateVerification {
489 fn default() -> Self {
490 Self(default_provider())
491 }
492 }
493
494 impl ServerCertVerifier for NoCertificateVerification {
495 fn verify_server_cert(
496 &self,
497 _end_entity: &CertificateDer<'_>,
498 _intermediates: &[CertificateDer<'_>],
499 _server_name: &ServerName<'_>,
500 _ocsp: &[u8],
501 _now: UnixTime,
502 ) -> Result<ServerCertVerified, rustls::Error> {
503 Ok(ServerCertVerified::assertion())
504 }
505
506 fn verify_tls12_signature(
507 &self,
508 message: &[u8],
509 cert: &CertificateDer<'_>,
510 dss: &DigitallySignedStruct,
511 ) -> Result<HandshakeSignatureValid, rustls::Error> {
512 verify_tls12_signature(
513 message,
514 cert,
515 dss,
516 &self.0.signature_verification_algorithms,
517 )
518 }
519
520 fn verify_tls13_signature(
521 &self,
522 message: &[u8],
523 cert: &CertificateDer<'_>,
524 dss: &DigitallySignedStruct,
525 ) -> Result<HandshakeSignatureValid, rustls::Error> {
526 verify_tls13_signature(
527 message,
528 cert,
529 dss,
530 &self.0.signature_verification_algorithms,
531 )
532 }
533
534 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
535 self.0.signature_verification_algorithms.supported_schemes()
536 }
537 }
538
539 pub(super) fn insecure_tls_config() -> ClientConfig {
540 let mut cfg = ClientConfig::builder_with_provider(Arc::new(
541 rustls::crypto::ring::default_provider(),
542 ))
543 .with_safe_default_protocol_versions()
544 .unwrap()
545 .with_root_certificates(RootCertStore::empty())
546 .with_no_client_auth();
547 cfg.dangerous()
548 .set_certificate_verifier(Arc::new(NoCertificateVerification::default()));
549 cfg
550 }
551 }
552}