Template passtrough for dns server information

This commit is contained in:
Slatian 2023-08-05 18:19:28 +02:00
parent cc6a025f89
commit 727d9a77cd
5 changed files with 146 additions and 48 deletions

View File

@ -64,5 +64,5 @@ burst = 15
[dns.resolver.digitalcourage]
display_name = "Digitalcourage 3"
servers = ["5.9.164.112:853","[2a01:4f8:251:554::2]:853"]
protocol = "Tls"
protocol = "tls"
tls_dns_name = "dns3.digitalcourage.de"

View File

@ -16,6 +16,7 @@ pub struct DnsConfig {
}
#[derive(Deserialize, Serialize, Clone)]
#[serde(rename_all="lowercase")]
pub enum DnsProtocol {
Udp,
Tcp,

View File

@ -50,13 +50,13 @@ use crate::geoip::{
LocationResult,
};
use crate::idna::IdnaName;
use crate::simple_dns::DnsLookupResult;
use crate::templating_engine::{
View,
ResponseFormat,
TemplateSettings,
Selectable,
};
use crate::ipinfo::{AddressCast,AddressInfo,AddressScope};
#[derive(Deserialize, Serialize, Clone)]
@ -85,6 +85,7 @@ pub struct IpResult {
asn: Option<AsnResult>,
location: Option<LocationResult>,
ip_info: AddressInfo,
used_dns_resolver: Option<String>,
}
// We need this one to hide the partial lookup field when irelevant
@ -97,18 +98,23 @@ pub struct DigResult {
idn: IdnaName,
#[serde(skip_serializing_if = "not")]
partial_lookup: bool,
used_dns_resolver: String,
}
struct ServiceSharedState {
templating_engine: templating_engine::Engine,
//dns_resolver: TokioAsyncResolver,
dns_resolvers: HashMap<String,TokioAsyncResolver>,
asn_db: geoip::MMDBCarrier,
location_db: geoip::MMDBCarrier,
config: config::EchoIpServiceConfig,
}
// Stores configuration that is derived from the original configuration
#[derive(Clone)]
struct DerivedConfiguration {
dns_resolver_selectables: Vec<Selectable>,
}
#[derive(Parser)]
#[command(author, version, long_about="A web service that tells you your ip-address and more …")]
struct CliArgs {
@ -234,6 +240,8 @@ async fn main() {
// Initalize DNS resolver with os defaults
println!("Initalizing dns resolver ...");
let mut dns_resolver_selectables = Vec::<Selectable>::new();
println!("Initalizing System resolver ...");
let res = TokioAsyncResolver::tokio_from_system_conf();
//let res = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
@ -245,22 +253,39 @@ async fn main() {
}
};
dns_resolver_selectables.push(Selectable {
id: "default".to_string(),
name: "System".to_string()
});
//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();
dns_resolver_selectables.push(Selectable {
id: "quad9".to_string(),
name: "Quad9".to_string()
});
println!("Initalizing Google resolver ...");
let google_resolver = TokioAsyncResolver::tokio(
trust_dns_resolver::config::ResolverConfig::google(),
Default::default()
).unwrap();
dns_resolver_selectables.push(Selectable {
id: "google".to_string(),
name: "Google".to_string()
});
println!("Initalizing Cloudflare resolver ...");
let cloudflare_resolver = TokioAsyncResolver::tokio(
trust_dns_resolver::config::ResolverConfig::cloudflare_tls(),
Default::default()
).unwrap();
dns_resolver_selectables.push(Selectable {
id: "cloudflare".to_string(),
name: "Cloudflare".to_string()
});
let mut dns_resolver_map: HashMap<String,TokioAsyncResolver> = HashMap::new();
@ -271,6 +296,10 @@ async fn main() {
Default::default()
).unwrap();
dns_resolver_map.insert(key.clone(), resolver);
dns_resolver_selectables.push(Selectable {
id: key.clone(),
name: resolver_config.display_name.clone(),
});
}
dns_resolver_map.insert("default".to_string(), dns_resolver);
@ -292,6 +321,10 @@ async fn main() {
config: config.clone(),
});
let derived_config = DerivedConfiguration {
dns_resolver_selectables: dns_resolver_selectables,
};
let signal_usr1_handlers_state = shared_state.clone();
task::spawn(async move {
@ -317,6 +350,8 @@ async fn main() {
.route("/", get(handle_default_route))
.route("/dig/:name", get(handle_dig_route_with_path))
.route("/ip/:address", get(handle_ip_route_with_path))
.route("/dns_resolver/:resolver", get(handle_dns_resolver_route_with_path))
.route("/dns_resolver", get(handle_dns_resolver_route))
.route("/ua", get(user_agent_handler))
.route("/hi", get(hello_world_handler))
.fallback_service(
@ -331,6 +366,7 @@ async fn main() {
config.ratelimit.per_minute, config.ratelimit.burst))
.layer(middleware::from_fn(ratelimit::rate_limit_middleware))
.layer(Extension(config))
.layer(Extension(derived_config))
.layer(middleware::from_fn(settings_query_middleware))
)
;
@ -347,6 +383,7 @@ async fn main() {
async fn settings_query_middleware<B>(
Query(query): Query<SettingsQuery>,
Extension(config): Extension<config::EchoIpServiceConfig>,
Extension(derived_config): Extension<DerivedConfiguration>,
user_agent_header: Option<TypedHeader<headers::UserAgent>>,
mut req: Request<B>,
next: Next<B>
@ -369,6 +406,7 @@ async fn settings_query_middleware<B>(
template: TemplateSettings{
format: format.unwrap_or(ResponseFormat::TextHtml),
lang: query.lang.unwrap_or("en".to_string()),
available_dns_resolvers: derived_config.dns_resolver_selectables,
},
dns_resolver_id: query.dns.unwrap_or(config.dns.default_resolver),
});
@ -377,24 +415,24 @@ async fn settings_query_middleware<B>(
async fn not_found_handler(
State(arc_state): State<Arc<ServiceSharedState>>,
Extension(settings): Extension<TemplateSettings>,
Extension(settings): Extension<QuerySettings>,
) -> Response {
let state = Arc::clone(&arc_state);
state.templating_engine.render_view(
&settings,
&settings.template,
&View::NotFound,
).await
}
async fn hello_world_handler(
State(arc_state): State<Arc<ServiceSharedState>>,
Extension(settings): Extension<TemplateSettings>,
Extension(settings): Extension<QuerySettings>,
) -> Response {
let state = Arc::clone(&arc_state);
state.templating_engine.render_view(
&settings,
&settings.template,
&View::Message{
title: "Hey There!".to_string(),
message: "You,You are an awesome Creature!".to_string()
@ -499,6 +537,37 @@ async fn handle_search_request(
}
async fn handle_dns_resolver_route(
Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>,
) -> Response {
let state = Arc::clone(&arc_state);
state.templating_engine.render_view(
&settings.template,
&View::DnsResolverList,
).await
}
async fn handle_dns_resolver_route_with_path(
Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>,
extract::Path(query): extract::Path<String>,
) -> Response {
let state = Arc::clone(&arc_state);
if let Some(resolver) = state.config.dns.resolver.get(&query) {
state.templating_engine.render_view(
&settings.template,
&View::DnsResolver{ config: resolver.clone() },
).await
} else {
state.templating_engine.render_view(
&settings.template,
&View::NotFound,
).await
}
}
async fn handle_ip_route_with_path(
Extension(settings): Extension<QuerySettings>,
State(arc_state): State<Arc<ServiceSharedState>>,
@ -547,20 +616,20 @@ async fn get_ip_result(
asn: None,
location: None,
ip_info: ip_info,
used_dns_resolver: None,
}
}
}
// do reverse lookup
let hostname = if state.config.dns.allow_reverse_lookup {
let mut hostname: Option<String> = None;
let mut used_dns_resolver: Option<String> = None;
if state.config.dns.allow_reverse_lookup {
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
simple_dns::reverse_lookup(&dns_resolver, &address).await
} else {
None
hostname = simple_dns::reverse_lookup(&dns_resolver, &address).await;
used_dns_resolver = Some(dns_resolver_name.clone());
}
} else {
None
};
}
// asn lookup
let asn_result = state.asn_db.query_asn_for_ip(address);
@ -572,23 +641,20 @@ async fn get_ip_result(
);
// filter reverse lookup
let final_hostname = match hostname {
Some(name) => {
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
None
} else {
Some(name.to_owned())
}
},
None => None,
};
if let Some(name) = &hostname {
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
hostname = None;
used_dns_resolver = None;
}
}
IpResult{
address: *address,
hostname: final_hostname,
hostname: hostname,
asn: asn_result,
location: location_result,
ip_info: ip_info,
used_dns_resolver: used_dns_resolver,
}
}
@ -630,11 +696,23 @@ async fn get_dig_result(
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 {
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
let idna_name = IdnaName::from_string(&name);
let idna_name = IdnaName::from_string(&name);
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
// Try to hide the fact that we didn't do dns resolution at all
// We resolve example.org as basic avoidance of timing sidechannels.
// WARNING: this timing sidechannel avoidance is very crude.
simple_dns::lookup(
&dns_resolver,
&("example.org.".to_string()),
do_full_lookup).await;
DigResult {
records: DnsLookupResult{ nxdomain: true , ..Default::default() },
idn: idna_name,
partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(),
}
} else {
DigResult {
records: simple_dns::lookup(
&dns_resolver,
@ -642,9 +720,10 @@ async fn get_dig_result(
do_full_lookup).await,
idn: idna_name,
partial_lookup: !do_full_lookup,
used_dns_resolver: dns_resolver_name.clone(),
}
} else {
return Default::default();
}
} else {
return Default::default();
}
}

View File

@ -27,20 +27,20 @@ use std::net::IpAddr;
#[derive(serde::Deserialize, serde::Serialize, Default, Clone)]
pub struct DnsLookupResult {
a: Option<Vec<IpAddr>>,
aaaa: Option<Vec<IpAddr>>,
aname: Option<Vec<String>>,
cname: Option<Vec<String>>,
mx: Option<Vec<MxRecord>>,
ns: Option<Vec<String>>,
soa: Option<Vec<SoaRecord>>,
txt: Option<Vec<String>>,
srv: Option<Vec<SrvRecord>>,
caa: Option<Vec<String>>,
other_error: bool,
dns_error: bool,
nxdomain: bool,
timeout: bool,
pub a: Option<Vec<IpAddr>>,
pub aaaa: Option<Vec<IpAddr>>,
pub aname: Option<Vec<String>>,
pub cname: Option<Vec<String>>,
pub mx: Option<Vec<MxRecord>>,
pub ns: Option<Vec<String>>,
pub soa: Option<Vec<SoaRecord>>,
pub txt: Option<Vec<String>>,
pub srv: Option<Vec<SrvRecord>>,
pub caa: Option<Vec<String>>,
pub other_error: bool,
pub dns_error: bool,
pub nxdomain: bool,
pub timeout: bool,
}
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]

View File

@ -15,6 +15,7 @@ use toml::Table;
use crate::DigResult;
use crate::IpResult;
use crate::config::DnsResolverConfig;
/* Response format */
@ -54,6 +55,13 @@ impl ResponseFormat {
pub struct TemplateSettings {
pub format: ResponseFormat,
pub lang: String,
pub available_dns_resolvers: Vec<Selectable>,
}
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct Selectable {
pub id: String,
pub name: String,
}
/* The echoip view */
@ -63,6 +71,8 @@ pub struct TemplateSettings {
pub enum View {
Asn { asn: u32 },
Dig { query: String, result: DigResult },
DnsResolver{ config: DnsResolverConfig },
DnsResolverList,
Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult },
Message{ title: String, message: String },
@ -75,6 +85,8 @@ impl View {
match self {
View::Asn{..} => "asn",
View::Dig{..} => "dig",
View::DnsResolver{..} => "dns_resolver",
View::DnsResolverList => "dns_resolver_list",
View::Index{..} => "index",
View::Ip{..} => "ip",
View::Message{..} => "message",
@ -133,6 +145,12 @@ impl Engine {
View::Index{result, ..} | View::Ip{result, ..} => {
Json(result).into_response()
},
View::DnsResolverList => {
Json(settings.available_dns_resolvers.clone()).into_response()
},
View::DnsResolver{ config } => {
Json(config).into_response()
}
_ => Json(view).into_response(),
}
}