From 0d0cbd38997a73b873044c5df53be9a8940fb179 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Thu, 19 Sep 2024 14:53:50 +0200 Subject: [PATCH] feat: resolve serve addresses with DNS --- Cargo.lock | 140 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + Trunk.toml | 4 +- schemas/config.json | 21 +++--- src/cmd/serve.rs | 7 ++ src/config/models/serve.rs | 4 ++ src/config/rt/serve.rs | 4 ++ src/serve/mod.rs | 74 ++++++++++++++++---- 8 files changed, 233 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c3809e1..ce32e9aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,6 +1005,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -1348,6 +1360,51 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hickory-proto" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1378,6 +1435,17 @@ dependencies = [ "windows", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "htmlescape" version = "0.3.1" @@ -1528,6 +1596,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -1578,6 +1656,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + [[package]] name = "ipnet" version = "2.10.0" @@ -1772,6 +1862,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1831,6 +1927,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "lzma-rs" version = "0.3.0" @@ -1841,6 +1946,12 @@ dependencies = [ "crc", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -2591,6 +2702,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.5" @@ -2897,6 +3014,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + [[package]] name = "rgb" version = "0.8.50" @@ -3921,6 +4048,7 @@ dependencies = [ "dunce", "flate2", "futures-util", + "hickory-resolver", "homedir", "htmlescape", "http", @@ -4080,7 +4208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] @@ -4566,6 +4694,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winsafe" version = "0.0.19" diff --git a/Cargo.toml b/Cargo.toml index e012969c..ad37bab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ directories = "5" dunce = "1" flate2 = "1" futures-util = { version = "0.3", default-features = false, features = ["sink"] } +hickory-resolver = { version = "0.24.1", features = ["system-config"] } homedir = "0.3.3" htmlescape = "0.3.1" http = "1.1" diff --git a/Trunk.toml b/Trunk.toml index 8c07ce54..039b824e 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -43,7 +43,9 @@ addresses = ["127.0.0.1"] # The port to serve on. port = 8080 # Aliases to serve, typically found in an /etc/hosts file. -aliases = ["http://localhost.mywebsite.com"] +# aliases = ["http://localhost.mywebsite.com"] +# Disable the reverse DNS lookup during startup +disable_address_lookup = false # Open a browser tab once the initial build is complete. open = false # Whether to disable fallback to index.html for missing files. diff --git a/schemas/config.json b/schemas/config.json index 3a3bc53a..b581e0bf 100644 --- a/schemas/config.json +++ b/schemas/config.json @@ -419,6 +419,19 @@ "format": "ip" } }, + "aliases": { + "description": "The aliases to serve on.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "disable_address_lookup": { + "description": "Disable the reverse DNS lookup during startup", + "default": false, + "type": "boolean" + }, "headers": { "description": "Additional headers to send in responses", "default": {}, @@ -454,14 +467,6 @@ "format": "uint16", "minimum": 0.0 }, - "aliases": { - "description": "The aliases to serve on.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, "prefer_address_family": { "anyOf": [ { diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index c2df5d7f..0bb7fe44 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -30,6 +30,10 @@ pub struct Serve { /// The aliases to serve on #[arg(long, env = "TRUNK_SERVE_ALIAS")] pub alias: Option>, + /// Disable the lookup of addresses serving on during startup + #[arg(long, env = "TRUNK_SERVE_DISABLE_ADDRESS_LOOKUP")] + #[arg(default_missing_value="true", num_args=0..=1)] + pub disable_address_lookup: Option, /// Open a browser tab once the initial build is complete [default: false] #[arg(long, env = "TRUNK_SERVE_OPEN")] #[arg(default_missing_value="true", num_args=0..=1)] @@ -110,6 +114,7 @@ impl Serve { prefer_address_family, port, alias, + disable_address_lookup, open, proxy: ProxyArgs { @@ -136,6 +141,8 @@ impl Serve { config.serve.addresses = address.unwrap_or(config.serve.addresses); config.serve.port = port.unwrap_or(config.serve.port); config.serve.aliases = alias.unwrap_or(config.serve.aliases); + config.serve.disable_address_lookup = + disable_address_lookup.unwrap_or(config.serve.disable_address_lookup); config.serve.open = open.unwrap_or(config.serve.open); config.serve.prefer_address_family = prefer_address_family.or(config.serve.prefer_address_family); diff --git a/src/config/models/serve.rs b/src/config/models/serve.rs index 8db9a78e..38de51fb 100644 --- a/src/config/models/serve.rs +++ b/src/config/models/serve.rs @@ -20,6 +20,9 @@ pub struct Serve { pub addresses: Vec, #[serde(default)] pub prefer_address_family: Option, + /// Disable the reverse DNS lookup during startup + #[serde(default)] + pub disable_address_lookup: bool, /// The port to serve on [default: 8080] #[serde(default = "default::port")] pub port: u16, @@ -90,6 +93,7 @@ impl Default for Serve { aliases: vec![], prefer_address_family: None, port: default::port(), + disable_address_lookup: false, open: false, no_autoreload: false, headers: Default::default(), diff --git a/src/config/rt/serve.rs b/src/config/rt/serve.rs index cb822297..29a580df 100644 --- a/src/config/rt/serve.rs +++ b/src/config/rt/serve.rs @@ -30,6 +30,8 @@ pub struct RtcServe { pub port: u16, /// The aliases to serve on. pub aliases: Vec, + /// Disable the DNS lookup during startup + pub disable_address_lookup: bool, /// Open a browser tab once the initial build is complete. pub open: bool, /// Any proxies configured to run along with the server. @@ -79,6 +81,7 @@ impl RtcServe { prefer_address_family, port, aliases, + disable_address_lookup, open: _, // auto-reload is handle by the builder options no_autoreload: _, @@ -110,6 +113,7 @@ impl RtcServe { addresses: build_address_list(prefer_address_family, addresses), port, aliases, + disable_address_lookup, open, proxies: config.proxies.0, no_spa, diff --git a/src/serve/mod.rs b/src/serve/mod.rs index 36f34113..98547fec 100644 --- a/src/serve/mod.rs +++ b/src/serve/mod.rs @@ -16,9 +16,10 @@ use axum::response::{IntoResponse, Response}; use axum::routing::{get, get_service, Router}; use axum_server::Handle; use futures_util::FutureExt; +use hickory_resolver::TokioAsyncResolver; use http::HeaderMap; use proxy::{ProxyBuilder, ProxyClientOptions}; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; @@ -29,6 +30,7 @@ use tokio::task::JoinHandle; use tower_http::services::{ServeDir, ServeFile}; use tower_http::set_header::SetResponseHeaderLayer; use tower_http::trace::TraceLayer; +use tracing::log; const INDEX_HTML: &str = "index.html"; @@ -81,7 +83,8 @@ impl ServeSystem { self.cfg.clone(), self.shutdown_tx.subscribe(), self.ws_state, - )?; + ) + .await?; // Open the browser. if self.cfg.open { @@ -116,7 +119,7 @@ impl ServeSystem { } #[tracing::instrument(level = "trace", skip(cfg, shutdown_rx))] - fn spawn_server( + async fn spawn_server( cfg: Arc, shutdown_rx: broadcast::Receiver<()>, ws_state: watch::Receiver, @@ -144,7 +147,14 @@ impl ServeSystem { .map(|alias| format!("{alias}:{}", cfg.port)) .collect::>(); - show_listening(&cfg, &addr, &aliases, &serve_base_url); + show_listening( + &cfg, + &addr, + &aliases, + &serve_base_url, + !cfg.disable_address_lookup, + ) + .await; let server = run_server(addr, cfg.tls.clone(), router, shutdown_rx); @@ -160,8 +170,19 @@ impl ServeSystem { } } -/// show where `serve` is listening -fn show_listening(cfg: &RtcServe, addr: &[SocketAddr], aliases: &[String], base: &str) { +/// Show where `serve` is listening +/// +/// We'll look up addresses, and simply append aliases. +async fn show_listening( + cfg: &RtcServe, + addr: &[SocketAddr], + aliases: &[String], + base: &str, + lookup: bool, +) { + // Only show what we didn't show so far + let mut cache = HashSet::new(); + let prefix = if cfg.tls.is_some() { "https" } else { "http" }; // prepare interface addresses @@ -189,7 +210,7 @@ fn show_listening(cfg: &RtcServe, addr: &[SocketAddr], aliases: &[String], base: } } - fn is_loopback(address: SocketAddr) -> bool { + fn is_loopback(address: &SocketAddr) -> bool { match address { SocketAddr::V4(addr) => addr.ip().is_loopback(), SocketAddr::V6(addr) => addr.ip().is_loopback(), @@ -198,14 +219,43 @@ fn show_listening(cfg: &RtcServe, addr: &[SocketAddr], aliases: &[String], base: tracing::info!("{SERVER}server listening at:"); - for address in addresses { - tracing::info!( - " {}{prefix}://{address}{base}", - if is_loopback(address) { LOCAL } else { NETWORK }, + for address in &addresses { + show_address( + &mut cache, + is_loopback(address), + format!("{prefix}://{address}{base}"), ); } for alias in aliases { - tracing::info!(" {LOCAL}{alias}"); + show_address(&mut cache, true, alias); + } + if lookup { + match TokioAsyncResolver::tokio_from_system_conf() { + Ok(resolver) => { + for address in &addresses { + let local = is_loopback(address); + if let Ok(names) = resolver.reverse_lookup(address.ip()).await { + for name in names { + show_address( + &mut cache, + local, + format!("{prefix}://{name}:{port}{base}", port = address.port()), + ); + } + } + } + } + Err(err) => { + log::warn!("Failed to create system resolver, skipping address resolution: {err}"); + } + } + } +} + +fn show_address(cache: &mut HashSet, local: bool, address: impl Into) { + let address = address.into(); + if cache.insert(address.clone()) { + tracing::info!(" {}{address}", if local { LOCAL } else { NETWORK }); } }