From d4caf1a77ce525373c9dec31ed4690f62d25ab32 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sun, 19 Feb 2023 22:05:49 +0100 Subject: [PATCH] =?UTF-8?q?All=20features=20in=20the=20config=20file=20wor?= =?UTF-8?q?k=20now=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- echoip_config.toml | 26 +++++------ echoip_full.toml | 42 ----------------- echoip_test.toml | 10 ++-- src/config.rs | 5 +- src/ipinfo.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 51 +++++++++++---------- 6 files changed, 160 insertions(+), 86 deletions(-) delete mode 100644 echoip_full.toml create mode 100644 src/ipinfo.rs diff --git a/echoip_config.toml b/echoip_config.toml index 2d49b0c..1d1d56d 100644 --- a/echoip_config.toml +++ b/echoip_config.toml @@ -1,37 +1,37 @@ [server] # What port to listen on and where request are supposed to come from -#listen_on: "127.0.0.1:3000" +listen_on = "127.0.0.1:3000" # What header your reverse proxy sets that contains the real ip-address # Possible Values: Every Vaiation of SecureClientIpSource in the axum_client_ip package # https://docs.rs/axum-client-ip/latest/axum_client_ip/enum.SecureClientIpSource.html #ip_header = "RightmostXForwardedFor" # When you don't want to use a proxy server: -#ip_header = "ConnectInfo" +ip_header = "ConnectInfo" + +# Allow querying of private range ips +# enable if you want to use this service +# on your internal network for some reason +allow_private_ip_lookup = false [dns] # Enable the /dig enpoint -#allow_forward_lookup = true +allow_forward_lookup = true # Enable reverse lookup, make sure to configure the hidden_suffixes # to contain your locally used domains, to prevent onformation leakage -#allow_reverse_lookup = false - -# Hide anything that has to do with private ip ranges -# Useful dor public services, disable if you want it -# on your internal network for some reason -#hide_private_range_ips = true +allow_reverse_lookup = false # echoip-sltecave will pretend that domains # that end with one of these suffixes don't exist -#hidden_suffixes = [".local"] +hidden_suffixes = [".local", ".box"] [geoip] # Path to geoip databses # Currently only the mmdb format is supported # Official databases can be obtained from maxmind.com -#asn_database = "mmdb/GeoLite2-ASN.mmdb" -#location_database = "mmdb/GeoLite2-City.mmdb" +asn_database = "mmdb/GeoLite2-ASN.mmdb" +location_database = "mmdb/GeoLite2-City.mmdb" # If anyone knows a free (as in freedom) groip database # please open an issue so I can integrate it @@ -39,4 +39,4 @@ [template] # Path to the template directory, can contain glob patterns -#template_location = "templates" +template_location = "templates" diff --git a/echoip_full.toml b/echoip_full.toml deleted file mode 100644 index a5af978..0000000 --- a/echoip_full.toml +++ /dev/null @@ -1,42 +0,0 @@ -[server] -# What port to listen on and where request are supposed to come from -listen_on = "127.0.0.1:3000" - -# What header your reverse proxy sets that contains the real ip-address -# Possible Values: Every Vaiation of SecureClientIpSource in the axum_client_ip package -# https://docs.rs/axum-client-ip/latest/axum_client_ip/enum.SecureClientIpSource.html -#ip_header = "RightmostXForwardedFor" -# When you don't want to use a proxy server: -ip_header = "ConnectInfo" - -[dns] -# Enable the /dig enpoint -allow_forward_lookup = true - -# Enable reverse lookup, make sure to configure the hidden_suffixes -# to contain your locally used domains, to prevent onformation leakage -allow_reverse_lookup = true - -# Hide anything that has to do with private ip ranges -# Useful dor public services, disable if you want it -# on your internal network for some reason -#hide_private_range_ips = true - -# echoip-sltecave will pretend that domains -# that end with one of these suffixes don't exist -hidden_suffixes = [".com"] - -[geoip] -# Path to geoip databses -# Currently only the mmdb format is supported -# Official databases can be obtained from maxmind.com -#asn_database = "mmdb/GeoLite2-ASN.mmdb" -#location_database = "mmdb/GeoLite2-City.mmdb" - -# If anyone knows a free (as in freedom) groip database -# please open an issue so I can integrate it -# https://codeberg.org/slatian/service.echoip-slatecave - -[template] -# Path to the template directory, can contain glob patterns -#template_location = "templates" diff --git a/echoip_test.toml b/echoip_test.toml index 62185f8..075843b 100644 --- a/echoip_test.toml +++ b/echoip_test.toml @@ -9,6 +9,11 @@ listen_on = "127.0.0.1:3000" # When you don't want to use a proxy server: ip_header = "ConnectInfo" +# Allow querying of private range ips +# enable if you want to use this service +# on your internal network for some reason +allow_private_ip_lookup = true + [dns] # Enable the /dig enpoint allow_forward_lookup = true @@ -17,11 +22,6 @@ allow_forward_lookup = true # to contain your locally used domains, to prevent onformation leakage allow_reverse_lookup = true -# Hide anything that has to do with private ip ranges -# Useful dor public services, disable if you want it -# on your internal network for some reason -hide_private_range_ips = true - # echoip-sltecave will pretend that domains # that end with one of these suffixes don't exist hidden_suffixes = [".com"] diff --git a/src/config.rs b/src/config.rs index 8419049..318d9a7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,13 +13,14 @@ pub struct EchoIpServiceConfig { pub struct ServerConfig { pub listen_on: SocketAddr, pub ip_header: SecureClientIpSource, + + pub allow_private_ip_lookup: bool, } #[derive(serde::Deserialize)] pub struct DnsConfig { pub allow_forward_lookup: bool, pub allow_reverse_lookup: bool, - pub hide_private_range_ips: bool, pub hidden_suffixes: Vec, //Future Idea: allow custom resolver } @@ -40,6 +41,7 @@ impl Default for ServerConfig { ServerConfig { listen_on: "127.0.0.1:3000".parse().unwrap(), ip_header: SecureClientIpSource::RightmostXForwardedFor, + allow_private_ip_lookup: false, } } } @@ -49,7 +51,6 @@ impl Default for DnsConfig { DnsConfig { allow_forward_lookup: true, allow_reverse_lookup: false, - hide_private_range_ips: true, hidden_suffixes: Vec::new(), } } diff --git a/src/ipinfo.rs b/src/ipinfo.rs new file mode 100644 index 0000000..bc710b6 --- /dev/null +++ b/src/ipinfo.rs @@ -0,0 +1,112 @@ +/* + * The code in this file should provide a simple way to categorize + * IP-Address ranges (based on the rust tandard library) and return the + * result in a template ready format that can be used for custom messages + * depending on the type of IP-Address. + */ + +use std::net::{IpAddr, Ipv4Addr}; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Default, PartialEq)] +#[serde(rename_all="lowercase")] +pub enum AddressCast { + Unspecified, + #[default] + Unicast, + Multicast, + Broadcast, +} + +#[derive(Serialize, Deserialize, Default, PartialEq)] +#[serde(rename_all="lowercase")] +pub enum AddressScope { + Global, + Private, + Shared, + LinkLocal, + Loopback, + Reserved, + Documentation, + #[default] + Unknown, +} + +#[derive(Serialize, Deserialize, Default)] +pub struct AddressInfo { + pub is_v6_address: bool, + pub cast: AddressCast, + pub scope: AddressScope, +} + +impl AddressInfo { + pub fn new(address: &IpAddr) -> Self { + let mut is_v6_address = false; + let mut address_cast = AddressCast::Unicast; + let mut address_scope = AddressScope::Unknown; + match address { + IpAddr::V4(addr) => { + let naddr : u32 = (*addr).into(); + let shared_net : u32 = Ipv4Addr::new(100, 64, 0, 0).into(); + let v4_10_mask : u32 = 0xffc00000; + if addr.is_documentation() { + address_scope = AddressScope::Documentation; + //test if this is a shared address + } else if naddr & v4_10_mask == shared_net { + address_scope = AddressScope::Shared; + } else if addr.is_link_local() { + address_scope = AddressScope::LinkLocal; + } else if addr.is_private() { + address_scope = AddressScope::Private; + } else if addr.is_broadcast() { + address_cast = AddressCast::Broadcast; + address_scope = AddressScope::LinkLocal; + } + }, + IpAddr::V6(addr) => { + is_v6_address = true; + let segments = addr.segments(); + // for std::net these are still nightly only api … 😕 + // Test for unique local addresses fc00::/7 + if segments[0] & 0xfe00 == 0xfc00 { + address_scope = AddressScope::Private; + // Test for link local addresses fe80::/10 + } else if segments[0] & 0xffc0 == 0xfe80 { + address_scope = AddressScope::LinkLocal; + // Test for the documentation address 2001:db8::/32 + } else if segments[0]==0x2001 && segments[1]==0x0db8 && segments[2]==0 && segments[3]==0 { + address_scope = AddressScope::Documentation; + // Test for multicase scope + } else if addr.is_multicast() { + address_cast = AddressCast::Multicast; + let cast_type = segments[1] & 0x0f; + match cast_type { + 1 => address_scope = AddressScope::Loopback, + 2 => address_scope = AddressScope::LinkLocal, + 4 | 5 | 8 => address_scope = AddressScope::Private, + 0xe => address_scope = AddressScope::Global, + 0 | 3 | 0xf => address_scope = AddressScope::Reserved, + _ => address_scope = AddressScope::Unknown, + } + } + + } + } + if address.is_loopback() { + address_scope = AddressScope::Loopback; + address_cast = AddressCast::Unicast; + } else if address.is_multicast() { + address_cast = AddressCast::Multicast; + } else if address.is_unspecified() { + address_scope = AddressScope::Unknown; + address_cast = AddressCast::Unspecified; + } + AddressInfo { + is_v6_address: is_v6_address, + cast: address_cast, + scope: address_scope + } + } +} + + diff --git a/src/main.rs b/src/main.rs index 439481e..1514661 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,13 @@ use axum::{ Router, routing::get, }; -use axum_client_ip::{SecureClientIp, SecureClientIpSource}; +use axum_client_ip::SecureClientIp; use clap::Parser; use tera::Tera; use trust_dns_resolver::{ TokioAsyncResolver, - config::ResolverOpts, - config::ResolverConfig, +// config::ResolverOpts, +// config::ResolverConfig, }; use std::fs; @@ -20,10 +20,11 @@ use std::net::IpAddr; use std::sync::Arc; use std::path::Path; +mod config; +mod geoip; +mod ipinfo; mod simple_dns; mod templating_engine; -mod geoip; -mod config; use crate::geoip::QueryAsn; use crate::geoip::QueryLocation; @@ -33,6 +34,8 @@ use geoip::LocationResult; use crate::templating_engine::View; use crate::templating_engine::ResponseFormat; +use crate::ipinfo::{AddressCast,AddressInfo,AddressScope}; + #[derive(serde::Deserialize, serde::Serialize)] pub struct BaseQuery { format: Option, @@ -57,6 +60,7 @@ pub struct IpResult { hostname: Option, asn: Option, location: Option, + ip_info: AddressInfo, } struct ServiceSharedState { @@ -163,7 +167,8 @@ async fn main() { }; let listen_on = config.server.listen_on; - + let ip_header = config.server.ip_header.clone(); + // Initialize shared state let shared_state = Arc::new( ServiceSharedState { @@ -185,7 +190,7 @@ async fn main() { .route("/ip/:address", get(handle_ip_route_with_path)) .route("/hi", get(hello_world_handler)) .with_state(shared_state) - .layer(SecureClientIpSource::RightmostXForwardedFor.into_extension()) + .layer(ip_header.into_extension()) ; println!("Starting Server ..."); @@ -223,23 +228,7 @@ async fn handle_default_route( 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, - }; + let result = get_ip_result(&ip_query, &state).await; state.templating_engine.render_view( format, @@ -287,6 +276,19 @@ async fn get_ip_result( ) -> IpResult { let address = ip_query.ip; + let ip_info = AddressInfo::new(&address); + + if !(ip_info.scope == AddressScope::Global || ip_info.scope == AddressScope::Shared) || ip_info.cast != AddressCast::Unicast { + if !((ip_info.scope == AddressScope::Private || ip_info.scope == AddressScope::LinkLocal) && state.config.server.allow_private_ip_lookup) { + return IpResult { + hostname: None, + asn: None, + location: None, + ip_info: ip_info, + } + } + } + // do reverse lookup let hostname = if state.config.dns.allow_reverse_lookup { simple_dns::reverse_lookup(&state.dns_resolver, &address).await @@ -319,6 +321,7 @@ async fn get_ip_result( hostname: final_hostname, asn: asn_result, location: location_result, + ip_info: ip_info, } }