From cd8c0455dc839966bb164afe911c79ac57c211d8 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sun, 23 Jul 2023 15:23:44 +0200 Subject: [PATCH] First prototype with multiple dns providers --- Cargo.lock | 103 ++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- README.md | 4 +- src/main.rs | 119 +++++++++++++++++++++++++++++++-------- src/templating_engine.rs | 4 +- 5 files changed, 201 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85f7058..c655941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + [[package]] name = "bitflags" version = "1.3.2" @@ -670,7 +676,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags 1.3.2", "bytes", "headers-core", @@ -1392,6 +1398,21 @@ dependencies = [ "quick-error", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1411,6 +1432,27 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.2", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1438,6 +1480,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.171" @@ -1581,6 +1633,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -1712,6 +1770,17 @@ dependencies = [ "syn 2.0.26", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -1862,12 +1931,16 @@ dependencies = [ "ipnet", "lazy_static", "rand", + "rustls", + "rustls-pemfile", "smallvec", "thiserror", "tinyvec", "tokio", + "tokio-rustls", "tracing", "url", + "webpki", ] [[package]] @@ -1883,11 +1956,14 @@ dependencies = [ "lru-cache", "parking_lot", "resolv-conf", + "rustls", "smallvec", "thiserror", "tokio", + "tokio-rustls", "tracing", "trust-dns-proto", + "webpki-roots", ] [[package]] @@ -1997,6 +2073,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.4.0" @@ -2115,6 +2197,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "widestring" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index beb5224..36e9e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,5 @@ toml = "0.7" tower = "0.4" tower-http = { version = "0.4", features = ["fs"] } trust-dns-proto = "0.22" -trust-dns-resolver = "0.22" +trust-dns-resolver = { version = "0.22", features = ["dns-over-rustls"] } maxminddb = "0.23" diff --git a/README.md b/README.md index 17074cc..852c41c 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ It is Licensed under the AGPL-v3 license. Simply run `cargo build` after cloning. The binary should be called `target/debug/echoip-slatecave`. -To make a release build (the one you want to have on your server) run `cargo build --relese`, the binary will end up in `target/release/echoip-slatecave`. - -NOTE: As of 2023-02-18 You need at least version 1.65 of the rust compiler. Consider using rustup. +To make a release build (the one you want to have on your server) run `cargo build --release`, the binary will end up in `target/release/echoip-slatecave`. ## Usage and configuration diff --git a/src/main.rs b/src/main.rs index 3f15a89..2ba8a40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ use tokio::signal::unix::{ }; use tokio::task; +use std::collections::HashMap; use std::fs; use std::net::IpAddr; use std::sync::Arc; @@ -72,6 +73,16 @@ pub struct SearchQuery { query: Option, } +pub fn default_dns_name() -> String { + "default".to_string() +} + +#[derive(serde::Deserialize, serde::Serialize, Clone)] +pub struct ResolverQuery { + #[serde(default="default_dns_name")] + dns: String, +} + #[derive(serde::Deserialize, serde::Serialize, Clone)] pub struct IpResult { address: IpAddr, @@ -96,7 +107,8 @@ pub struct DigResult { struct ServiceSharedState { templating_engine: templating_engine::Engine, - dns_resolver: TokioAsyncResolver, + //dns_resolver: TokioAsyncResolver, + dns_resolvers: HashMap, asn_db: geoip::MMDBCarrier, location_db: geoip::MMDBCarrier, config: config::EchoIpServiceConfig, @@ -238,6 +250,30 @@ async fn main() { } }; + //FIXME: Not release ready,must be configurable and have better error handling. + println!("Initalizing Quad9 resolver ..."); + let quad9_resolver = TokioAsyncResolver::tokio( + trust_dns_resolver::config::ResolverConfig::quad9_tls(), + Default::default() + ).unwrap(); + println!("Initalizing Google resolver ..."); + let google_resolver = TokioAsyncResolver::tokio( + trust_dns_resolver::config::ResolverConfig::google(), + Default::default() + ).unwrap(); + println!("Initalizing Cloudflare resolver ..."); + let cloudflare_resolver = TokioAsyncResolver::tokio( + trust_dns_resolver::config::ResolverConfig::cloudflare_tls(), + Default::default() + ).unwrap(); + + let mut dns_resolver_map: HashMap = HashMap::new(); + + dns_resolver_map.insert("default".to_string(), dns_resolver); + dns_resolver_map.insert("quad9".to_string(), quad9_resolver); + dns_resolver_map.insert("google".to_string(), google_resolver); + dns_resolver_map.insert("cloudflare".to_string(), cloudflare_resolver); + let listen_on = config.server.listen_on; let ip_header = config.server.ip_header.clone(); @@ -245,7 +281,8 @@ async fn main() { let shared_state = Arc::new( ServiceSharedState { templating_engine: templating_engine, - dns_resolver: dns_resolver, + //dns_resolver: dns_resolver, + dns_resolvers: dns_resolver_map, asn_db: asn_db, location_db: location_db, config: config.clone(), @@ -294,7 +331,7 @@ async fn main() { ) ; - println!("Starting Server ..."); + println!("Starting Server on {} ...",listen_on); axum::Server::bind(&listen_on) .serve(app.into_make_service_with_connect_info::()) @@ -367,6 +404,7 @@ async fn user_agent_handler( async fn handle_default_route( Query(search_query): Query, + Query(resolver_settings): Query, State(arc_state): State>, Extension(settings): Extension, user_agent_header: Option>, @@ -377,11 +415,17 @@ async fn handle_default_route( if let Some(search_query) = search_query.query { if search_query.trim() != "" { - return handle_search_request(search_query, false, settings, state).await; + return handle_search_request( + search_query, + false, + settings, + resolver_settings, + state + ).await; } } - let result = get_ip_result(&address, &settings.lang, &state).await; + let result = get_ip_result(&address, &settings.lang, &"default".to_string(), &state).await; let user_agent: Option = match user_agent_header { Some(TypedHeader(user_agent)) => Some(user_agent.to_string()), @@ -402,6 +446,7 @@ async fn handle_search_request( search_query: String, this_should_have_been_an_ip: bool, settings: TemplateSettings, + resolver_settings: ResolverQuery, arc_state: Arc, ) -> Response { @@ -425,12 +470,15 @@ async fn handle_search_request( // Try to interpret as an IP-Address if let Ok(address) = search_query.parse() { - return handle_ip_request(address, settings, arc_state).await; + return handle_ip_request(address, settings, resolver_settings, arc_state).await; } // Fall back to treating it as a hostname return handle_dig_request( - search_query.to_string(), settings, arc_state, + search_query.to_string(), + settings, + resolver_settings, + arc_state, !this_should_have_been_an_ip, ).await @@ -439,23 +487,29 @@ async fn handle_search_request( async fn handle_ip_route_with_path( Extension(settings): Extension, State(arc_state): State>, + Query(resolver_settings): Query, extract::Path(query): extract::Path, ) -> Response { if let Ok(address) = query.parse() { - return handle_ip_request(address, settings, arc_state).await + return handle_ip_request(address, settings, resolver_settings, arc_state).await } else { - return handle_search_request(query, true, settings, arc_state).await; + return handle_search_request(query, true, settings, resolver_settings, arc_state).await; } } async fn handle_ip_request( address: IpAddr, settings: TemplateSettings, + resolver_settings: ResolverQuery, arc_state: Arc, ) -> Response { let state = Arc::clone(&arc_state); - let result = get_ip_result(&address, &settings.lang, &state).await; + let result = get_ip_result( + &address, + &settings.lang, + &resolver_settings.dns, + &state).await; state.templating_engine.render_view( &settings, @@ -466,6 +520,7 @@ async fn handle_ip_request( async fn get_ip_result( address: &IpAddr, lang: &String, + dns_resolver_name: &String, state: &ServiceSharedState, ) -> IpResult { @@ -485,7 +540,11 @@ async fn get_ip_result( // do reverse lookup let hostname = if state.config.dns.allow_reverse_lookup { - simple_dns::reverse_lookup(&state.dns_resolver, &address).await + if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { + simple_dns::reverse_lookup(&dns_resolver, &address).await + } else { + None + } } else { None }; @@ -521,23 +580,30 @@ async fn get_ip_result( } async fn handle_dig_route_with_path( + Query(resolver_settings): Query, Extension(settings): Extension, State(arc_state): State>, extract::Path(name): extract::Path, ) -> Response { - return handle_dig_request(name, settings, arc_state, true).await + return handle_dig_request(name, settings, resolver_settings, arc_state, true).await } async fn handle_dig_request( dig_query: String, settings: TemplateSettings, + resolver_settings: ResolverQuery, arc_state: Arc, do_full_lookup: bool, ) -> Response { let state = Arc::clone(&arc_state); - let dig_result = get_dig_result(&dig_query, &state, do_full_lookup).await; + let dig_result = get_dig_result( + &dig_query, + &resolver_settings.dns, + &state, + do_full_lookup + ).await; state.templating_engine.render_view( &settings, @@ -547,22 +613,27 @@ async fn handle_dig_request( } async fn get_dig_result( - dig_query: &String, - state: &ServiceSharedState, - do_full_lookup: bool, + dig_query: &String, + dns_resolver_name: &String, + state: &ServiceSharedState, + do_full_lookup: bool, ) -> DigResult { let name = &dig_query.trim().trim_end_matches(".").to_string(); if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) { Default::default() } else { - let idna_name = IdnaName::from_string(&name); - DigResult { - records: simple_dns::lookup( - &state.dns_resolver, - &(idna_name.idn.clone().unwrap_or(name.to_owned())+"."), - do_full_lookup).await, - idn: idna_name, - partial_lookup: !do_full_lookup, + if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) { + let idna_name = IdnaName::from_string(&name); + DigResult { + records: simple_dns::lookup( + &dns_resolver, + &(idna_name.idn.clone().unwrap_or(name.to_owned())+"."), + do_full_lookup).await, + idn: idna_name, + partial_lookup: !do_full_lookup, + } + } else { + return Default::default(); } } } diff --git a/src/templating_engine.rs b/src/templating_engine.rs index 604db9f..6f7239b 100644 --- a/src/templating_engine.rs +++ b/src/templating_engine.rs @@ -116,10 +116,10 @@ impl Engine { _ => text.into_response(), } Err(e) => { - println!("There was an error while rendering index.html: {e:?}"); + println!("There was an error while rendering template {template_name}: {e:?}"); ( StatusCode::INTERNAL_SERVER_ERROR, - "Template error, contact owner or see logs.\n" + format!("Template error in {template_name}, contact owner or see logs.\n") ).into_response() } }