All features in the config file work now 🎉

This commit is contained in:
Slatian 2023-02-19 22:05:49 +01:00
parent ee19071a3d
commit d4caf1a77c
6 changed files with 160 additions and 86 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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"]

View File

@ -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<String>,
//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(),
}
}

112
src/ipinfo.rs Normal file
View File

@ -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
}
}
}

View File

@ -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<ResponseFormat>,
@ -57,6 +60,7 @@ pub struct IpResult {
hostname: Option<String>,
asn: Option<AsnResult>,
location: Option<LocationResult>,
ip_info: AddressInfo,
}
struct ServiceSharedState {
@ -163,6 +167,7 @@ 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(
@ -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,
}
}