From 2394d90087750535eb7326f28560a9b3cbbee1b4 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sat, 18 Feb 2023 18:07:52 +0100 Subject: [PATCH] Made querying with paths possible (for wasier commandline usage) --- src/config.rs | 57 +++++++++++++++++++++++++ src/geoip.rs | 16 +++---- src/main.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 src/config.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6d238cc --- /dev/null +++ b/src/config.rs @@ -0,0 +1,57 @@ +use std::path::Path; +use axum_client_ip::SecureClientIpSource; +use std::net::SocketAddr; + +#[derive(serde::Deserialize)] +pub struct EchoIpServiceConfig { + server: ServerConfig, + dns: DnsConfig, + geoip: GeoIpConfig, + template: TemplateConfig, +} + +#[derive(serde::Deserialize)] +pub struct ServerConfig { + listen_on: SocketAddr, + ip_header: SecureClientIpSource, +} + +#[derive(serde::Deserialize)] +pub struct DnsConfig { + allow_forward_lookup: bool, + allow_reverse_lookup: bool, + hide_private_range_ips: bool, + hidden_suffixes: Vec, + //Future Idea: allow custom resolver +} + +#[derive(serde::Deserialize)] +pub struct GeoIpConfig { + asn_database: Option, + location_database: Option, +} + +#[derive(serde::Deserialize)] +pub struct TemplateConfig { + template_location: String, +} + +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + listen_on: "127.0.0.1:3000", + ip_header: SecureClientIpSource::RightmostXForwardedFor, + } + } +} + +impl Default for DnsConfig { + fn default() -> Self { + DnsConfig { + allow_forward_lookup: true; + allow_reverse_lookup: false; + hide_private_range_ips: true; + hidden_suffixes: Vec::new(); + } + } +} diff --git a/src/geoip.rs b/src/geoip.rs index 6d284b9..bc01b74 100644 --- a/src/geoip.rs +++ b/src/geoip.rs @@ -50,7 +50,7 @@ pub struct MMDBCarrier { } pub trait QueryLocation { - fn query_location_for_ip(&self, address: IpAddr, laguages: &Vec) -> Option; + fn query_location_for_ip(&self, address: IpAddr, laguages: &Vec<&String>) -> Option; } pub trait QueryAsn { @@ -61,7 +61,7 @@ pub trait QueryAsn { pub fn extract_localized_name( names: &Option>, - languages: &Vec) + languages: &Vec<&String>) -> Option { match names { Some(names) => { @@ -76,7 +76,7 @@ names: &Option>, } } -pub fn geoip2_city_to_named_location(item: geoip2::model::City, languages: &Vec) -> NamedLocation { +pub fn geoip2_city_to_named_location(item: geoip2::model::City, languages: &Vec<&String>) -> NamedLocation { NamedLocation { iso_code: None, geoname_id: item.geoname_id, @@ -84,7 +84,7 @@ pub fn geoip2_city_to_named_location(item: geoip2::model::City, languages: &Vec< } } -pub fn geoip2_continent_to_named_location(item: geoip2::model::Continent, languages: &Vec) -> NamedLocation { +pub fn geoip2_continent_to_named_location(item: geoip2::model::Continent, languages: &Vec<&String>) -> NamedLocation { NamedLocation { iso_code: item.code.map(ToString::to_string), geoname_id: item.geoname_id, @@ -92,7 +92,7 @@ pub fn geoip2_continent_to_named_location(item: geoip2::model::Continent, langua } } -pub fn geoip2_country_to_named_location(item: geoip2::model::Country, languages: &Vec) -> NamedLocation { +pub fn geoip2_country_to_named_location(item: geoip2::model::Country, languages: &Vec<&String>) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, @@ -100,7 +100,7 @@ pub fn geoip2_country_to_named_location(item: geoip2::model::Country, languages: } } -pub fn geoip2_represented_country_to_named_location(item: geoip2::model::RepresentedCountry, languages: &Vec) -> NamedLocation { +pub fn geoip2_represented_country_to_named_location(item: geoip2::model::RepresentedCountry, languages: &Vec<&String>) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, @@ -108,7 +108,7 @@ pub fn geoip2_represented_country_to_named_location(item: geoip2::model::Represe } } -pub fn geoip2_subdivision_to_named_location(item: geoip2::model::Subdivision, languages: &Vec) -> NamedLocation { +pub fn geoip2_subdivision_to_named_location(item: geoip2::model::Subdivision, languages: &Vec<&String>) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, @@ -141,7 +141,7 @@ impl QueryAsn for MMDBCarrier { } impl QueryLocation for MMDBCarrier { - fn query_location_for_ip(&self, address: IpAddr, languages: &Vec) -> Option { + fn query_location_for_ip(&self, address: IpAddr, languages: &Vec<&String>) -> Option { match &self.mmdb { Some(mmdb) => { match mmdb.lookup::(address) { diff --git a/src/main.rs b/src/main.rs index a21d7b5..fce82ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ use axum::{ extract::Query, extract::State, + extract, response::Response, Router, routing::get, }; +use axum_client_ip::{SecureClientIp, SecureClientIpSource}; use tera::Tera; use trust_dns_resolver::{ @@ -13,13 +15,14 @@ use trust_dns_resolver::{ config::ResolverConfig, }; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::IpAddr; use std::sync::Arc; use std::path::Path; mod simple_dns; mod templating_engine; mod geoip; +mod config; use crate::geoip::QueryAsn; use crate::geoip::QueryLocation; @@ -30,15 +33,22 @@ use crate::templating_engine::View; use crate::templating_engine::ResponseFormat; #[derive(serde::Deserialize, serde::Serialize)] -pub struct IpQuery { - ip: Option, +pub struct BaseQuery { format: Option, + lang: Option, +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct IpQuery { + format: Option, + lang: Option, + ip: IpAddr, } #[derive(serde::Deserialize, serde::Serialize)] pub struct DigQuery { - name: String, format: Option, + name: String, } #[derive(serde::Deserialize, serde::Serialize)] @@ -48,6 +58,7 @@ pub struct IpResult { location: Option, } + struct ServiceSharedState { templating_engine: templating_engine::Engine, dns_resolver: TokioAsyncResolver, @@ -105,13 +116,17 @@ async fn main() { asn_db: asn_db, location_db: location_db, }); - + // Initalize axum server let app = Router::new() .route("/", get(handle_default_route)) .route("/dig", get(handle_dig_route)) + .route("/dig/:name", get(handle_dig_route_with_path)) + .route("/ip", get(handle_ip_route)) + .route("/ip/:address", get(handle_ip_route_with_path)) .route("/hi", get(hello_world_handler)) .with_state(shared_state) + .layer(SecureClientIpSource::RightmostXForwardedFor.into_extension()) ; println!("Starting Server ..."); @@ -134,13 +149,19 @@ async fn hello_world_handler( } async fn handle_default_route( - Query(ip_query): Query, + Query(query): Query, State(arc_state): State>, + SecureClientIp(address): SecureClientIp ) -> 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 = query.format.unwrap_or(ResponseFormat::TextHtml); + let ip_query = IpQuery { + format: query.format, + lang: query.lang, + ip: address, + }; + let state = Arc::clone(&arc_state); // do reverse lookup @@ -150,7 +171,10 @@ async fn handle_default_route( let asn_result = state.asn_db.query_asn_for_ip(address); // location lookup - let location_result = state.location_db.query_location_for_ip(address, &vec!["en".to_string()]); + let location_result = state.location_db.query_location_for_ip( + address, + &vec![&ip_query.lang.as_ref().unwrap_or(&"en".to_string()), &"en".to_string()] + ); let result = IpResult{ hostname: hostname.await, @@ -164,11 +188,82 @@ async fn handle_default_route( ).await } +async fn handle_ip_route( + Query(ip_query): Query, + State(arc_state): State>, +) -> Response { + return handle_ip_request(ip_query, arc_state).await +} + +async fn handle_ip_route_with_path( + Query(query): Query, + State(arc_state): State>, + extract::Path(address): extract::Path, +) -> Response { + return handle_ip_request(IpQuery { + format: query.format, + lang: query.lang, + ip: address, + }, arc_state).await +} + +async fn handle_ip_request( + ip_query: IpQuery, + arc_state: Arc, +) -> Response { + + let address = ip_query.ip; + let format = ip_query.format.unwrap_or(ResponseFormat::TextHtml); + + let state = Arc::clone(&arc_state); + + // do reverse lookup + let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address); + + // asn lookup + let asn_result = state.asn_db.query_asn_for_ip(address); + + // location lookup + let location_result = state.location_db.query_location_for_ip( + address, + &vec![&ip_query.lang.as_ref().unwrap_or(&"en".to_string()), &"en".to_string()] + ); + + let result = IpResult{ + hostname: hostname.await, + asn: asn_result, + location: location_result, + }; + + state.templating_engine.render_view( + format, + View::Ip{query: ip_query, result: result} + ).await +} + async fn handle_dig_route( Query(dig_query): Query, State(arc_state): State>, ) -> Response { - + return handle_dig_request(dig_query, arc_state).await +} + +async fn handle_dig_route_with_path( + Query(query): Query, + State(arc_state): State>, + extract::Path(name): extract::Path, +) -> Response { + return handle_dig_request(DigQuery { + format: query.format, + name: name, + }, arc_state).await +} + +async fn handle_dig_request( + dig_query: DigQuery, + arc_state: Arc, +) -> Response { + let state = Arc::clone(&arc_state); let name = &dig_query.name; let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml);