diff --git a/src/main.rs b/src/main.rs index f75e422..c24c961 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,9 @@ use axum::{ extract::State, http::StatusCode, response::Html, + response::IntoResponse, + response::Response, + response::Json, Router, routing::get, }; @@ -11,13 +14,15 @@ use trust_dns_resolver::{ TokioAsyncResolver, config::ResolverOpts, config::ResolverConfig, - error::*, }; + use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; -#[derive(serde::Deserialize, serde::Serialize)] +mod simple_dns; + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy)] enum ResponseFormat { #[serde(rename="text/plain", alias="text")] TextPlain, @@ -30,10 +35,10 @@ enum ResponseFormat { impl ToString for ResponseFormat { fn to_string(&self) -> String { match self { - ResponseFormat::TextPlain => "text/plain".to_string(), - ResponseFormat::TextHtml => "text/html".to_string(), - ResponseFormat::ApplicationJson => "application/json".to_string(), - } + ResponseFormat::TextPlain => "text/plain", + ResponseFormat::TextHtml => "text/html", + ResponseFormat::ApplicationJson => "application/json", + }.to_string() } } @@ -46,27 +51,47 @@ struct IpQuery { #[derive(serde::Deserialize, serde::Serialize)] struct DigQuery { name: String, - format: Option + format: Option, } #[derive(serde::Deserialize, serde::Serialize)] -struct MxRecord { - preference: u16, - exchange: String, +struct IpResult { + hostname: Option, } -#[derive(serde::Deserialize, serde::Serialize)] -struct DigResult { - a: Vec, - aaaa: Vec, - mx: Vec, +struct TemplatingEngine { + tera: Tera, } struct ServiceSharedState { - tera: Tera, - dns_resolver: TokioAsyncResolver, + templating_engine: TemplatingEngine, + dns_resolver: TokioAsyncResolver, } +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +enum EchoipView { + Dig { query: DigQuery, result: simple_dns::DnsLookupResult }, + Index { query: IpQuery, result: IpResult }, + Ip { query: IpQuery, result: IpResult }, + Message(String), + #[serde(rename="404")] + NotFound, +} + +impl EchoipView { + fn template_name(&self) -> String { + match self { + EchoipView::Dig{..} => "dig", + EchoipView::Index{..} => "index", + EchoipView::Ip{..} => "ip", + EchoipView::Message(..) => "message", + EchoipView::NotFound => "404", + }.to_string() + } +} + + #[tokio::main] async fn main() { // Initalize Tera templates @@ -94,7 +119,9 @@ async fn main() { // Initialize shared state let shared_state = Arc::new(ServiceSharedState{ - tera: tera, + templating_engine: TemplatingEngine{ + tera: tera, + }, dns_resolver: dns_resolver, }); @@ -114,130 +141,94 @@ async fn main() { .unwrap(); } -async fn hello_world_handler() -> &'static str { - "Hello, there, you, awesome creature!" +async fn hello_world_handler( + State(arc_state): State>, +) -> Response { + let state = Arc::clone(&arc_state); + + state.templating_engine.render_view( + ResponseFormat::TextPlain, + EchoipView::Message("Hello! There, You, Awesome Creature!".to_string()) + ).await } -async fn simple_reverse_dns_lookup( - resolver: &TokioAsyncResolver, - address: &IpAddr, -) -> Option { - let revese_res = resolver.reverse_lookup(*address); - match revese_res.await { - Ok(lookup) => { - for name in lookup { - return Some(name.to_string()) - } - None - } - Err(e) => { - let kind = e.kind(); - match kind { - ResolveErrorKind::NoRecordsFound { .. } => { - //Ignore, that just happens … - } - _ => { - println!("Reverse lookup on {address} failed: {kind}"); +impl TemplatingEngine { + async fn render_view( + &self, + format: ResponseFormat, + view: EchoipView, + ) -> Response { + match format { + ResponseFormat::TextHtml => { + let template_name = view.template_name(); + + let mut context = tera::Context::new(); + context.insert("view", &template_name); + //intented for shared macros + context.insert("format", &format.to_string()); + context.insert("data", &view); + + match self.tera.render(&(template_name+".html"), &context) { + Ok(html) => Html(html).into_response(), + Err(e) => { + println!("There was an error while rendering index.html: {e:?}"); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } + } + //TODO: Plain Text should have its own matcher + ResponseFormat::ApplicationJson | ResponseFormat::TextPlain => { + match view { + EchoipView::Dig{result, ..} => { + Json(result).into_response() + }, + EchoipView::Index{result, ..} | EchoipView::Ip{result, ..} => { + Json(result).into_response() + }, + _ => Json(view).into_response(), } } - None } - } + } } async fn handle_default_route( Query(ip_query): Query, State(arc_state): State>, -) -> Result,StatusCode> { +) -> Response { let address = ip_query.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))); let format = ip_query.format.unwrap_or(ResponseFormat::TextHtml); - let format_string = format.to_string(); let state = Arc::clone(&arc_state); // do reverse lookup - let hostname = simple_reverse_dns_lookup(&state.dns_resolver, &address); + let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address); - let mut context = tera::Context::new(); - context.insert("ip", &address); - context.insert("format", &format_string); - context.insert("hostname", &hostname.await); + let result = IpResult{ + hostname: hostname.await, + }; - match state.tera.render("index.html", &context) { - Ok(html) => Ok(Html(html)), - Err(e) => { - println!("There was an error while rendering index.html: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) - } - } + state.templating_engine.render_view( + format, + EchoipView::Index{query: ip_query, result: result} + ).await } async fn handle_dig_route( Query(dig_query): Query, State(arc_state): State>, -) -> Result,StatusCode> { +) -> Response { let state = Arc::clone(&arc_state); - let name = dig_query.name; + let name = &dig_query.name; + let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml); - let ipv4_lookup_res = state.dns_resolver.ipv4_lookup(&name); - let ipv6_lookup_res = state.dns_resolver.ipv6_lookup(&name); - let mx_lookup_res = state.dns_resolver.mx_lookup(&name); + let dig_result = simple_dns::lookup(&state.dns_resolver, name, true).await; - let mut dig_result = DigResult{ - a: Vec::new(), - aaaa: Vec::new(), - mx: Vec::new(), - }; + state.templating_engine.render_view( + format, + EchoipView::Dig{ query: dig_query, result: dig_result} + ).await - match ipv4_lookup_res.await { - Ok(lookup) => { - for address in lookup { - dig_result.a.push(std::net::IpAddr::V4(address)); - } - } - Err(e) => { - println!("There was an error while looking A up {name}: {e}"); - } - } - - match ipv6_lookup_res.await { - Ok(lookup) => { - for address in lookup { - dig_result.aaaa.push(std::net::IpAddr::V6(address)); - } - } - Err(e) => { - println!("There was an error while looking AAAA up {name}: {e}"); - } - } - - match mx_lookup_res.await { - Ok(lookup) => { - for mx in lookup { - dig_result.mx.push(MxRecord{ - preference: mx.preference(), - exchange: mx.exchange().to_string(), - }); - } - } - Err(e) => { - println!("There was an error while looking MX up {name}: {e}"); - } - } - - - let mut context = tera::Context::new(); - context.insert("dig_query", &name); - context.insert("dig_result", &dig_result); - - match state.tera.render("dig.html", &context) { - Ok(html) => Ok(Html(html)), - Err(e) => { - println!("There was an error while rendering index.html: {e}"); - Err(StatusCode::INTERNAL_SERVER_ERROR) - } - } - } diff --git a/src/simple_dns.rs b/src/simple_dns.rs new file mode 100644 index 0000000..376ac98 --- /dev/null +++ b/src/simple_dns.rs @@ -0,0 +1,118 @@ +/* + * This module wraps the trust_dns_resolver library + * to generate results thaat are ready for serializing + * or templating. + * It does not aim to be reusable for any other purpose, + * the trust_dns_resolver library already does that. + */ + +use trust_dns_resolver::{ + TokioAsyncResolver, + error::*, +}; + +use std::net::IpAddr; + + +/* Data Structures */ + +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(Default)] +pub struct DnsLookupResult { + a: Vec, + aaaa: Vec, + mx: Vec, +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct MxRecord { + preference: u16, + exchange: String, +} + +/* Lookup Functions*/ + +pub async fn reverse_lookup( + resolver: &TokioAsyncResolver, + address: &IpAddr, +) -> Option { + let revese_res = resolver.reverse_lookup(*address); + match revese_res.await { + Ok(lookup) => { + for name in lookup { + return Some(name.to_string()) + } + None + } + Err(e) => { + let kind = e.kind(); + match kind { + ResolveErrorKind::NoRecordsFound { .. } => { + //Ignore, that just happens … + } + _ => { + println!("Reverse lookup on {address} failed: {kind}"); + } + } + None + } + } +} + +// This function takes a resolver, a domain name and returns a DnsLookupResult. +// If do_full_lookup is false only the A and AAAA (CNAMEs planned for the future) +// records will be fetched. +pub async fn lookup( + resolver: &TokioAsyncResolver, + name: &String, + do_full_lookup: bool, +) -> DnsLookupResult { + let ipv4_lookup_res = resolver.ipv4_lookup(name); + let ipv6_lookup_res = resolver.ipv6_lookup(name); + + // initlize an empty lookup result + let mut dig_result: DnsLookupResult = Default::default(); + + match ipv4_lookup_res.await { + Ok(lookup) => { + for address in lookup { + dig_result.a.push(std::net::IpAddr::V4(address)); + } + } + Err(e) => { + println!("There was an error while looking A up {name}: {e}"); + } + } + + match ipv6_lookup_res.await { + Ok(lookup) => { + for address in lookup { + dig_result.aaaa.push(std::net::IpAddr::V6(address)); + } + } + Err(e) => { + println!("There was an error while looking AAAA up {name}: {e}"); + } + } + + if do_full_lookup { + let mx_lookup_res = resolver.mx_lookup(name); + + match mx_lookup_res.await { + Ok(lookup) => { + for mx in lookup { + dig_result.mx.push(MxRecord{ + preference: mx.preference(), + exchange: mx.exchange().to_string(), + }); + } + } + Err(e) => { + println!("There was an error while looking MX up {name}: {e}"); + } + } + } + + return dig_result + +} diff --git a/templates/dig.html b/templates/dig.html index 3d94af6..d599ae9 100644 --- a/templates/dig.html +++ b/templates/dig.html @@ -1,39 +1,39 @@ +{% set r = data.result %} +{% set q = data.query %} - Dig: {{ dig_query }} + Dig: {{ q.name }} -

Lookup for: {{ dig_query }}

- {% if dig_result.a %} +

Lookup for: {{ q.name }}

+ {% if r.a %}

A (IPv4) records:

    - {% for address in dig_result.a%} + {% for address in r.a%}
  • {{address}}
  • {% endfor %}
{% endif %} - {% if dig_result.aaaa %} + {% if r.aaaa %}

AAAA (IPv6) records:

    - {% for address in dig_result.aaaa%} + {% for address in r.aaaa%}
  • {{address}}
  • {% endfor %}
{% endif %} - {% if dig_result.mx %} + {% if r.mx %}

MX (Mail Exchange) records:

    - {% for mx in dig_result.mx%} + {% for mx in r.mx%}
  • {{mx.preference}} {{mx.exchange}}
  • {% endfor %}
{% endif %} - - diff --git a/templates/index.html b/templates/index.html index 1445bc2..a2cc952 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,13 +1,13 @@ - Your IP: {{ ip }} + Your IP: {{ data.query.ip }} -

Your IP-Address is: {{ ip }}

+

Your IP-Address is: {{ data.query.ip }}

Your requested format was: {{format}}

{% if hostname %} -

Hostname: {{hostname}}

+

Hostname: {{data.result.hostname}}

{% endif %}