mirror of
https://codeberg.org/slatian/service.echoip-slatecave.git
synced 2025-07-17 14:33:27 +02:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
7130c0d94a | |||
47fad2007b | |||
ba34caf8fc | |||
caf47522e4 | |||
b98bb67b4c | |||
d902dae35d | |||
2aae2d6626 | |||
4079e24c43 | |||
2b0c4eb3fb | |||
8d055682b6 | |||
ff8d86ff1d | |||
ce7632d443 | |||
cf82db3e87 | |||
fecbe68c7a |
813
Cargo.lock
generated
813
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@ -1,30 +1,32 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "echoip-slatecave"
|
name = "echoip-slatecave"
|
||||||
version = "1.2.4"
|
version = "1.6.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Slatian <baschdel@disroot.org>"]
|
authors = ["Slatian <baschdel@disroot.org>"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lib-humus = { version="0.2", features=["axum-view+cookie"] }
|
lib-humus = { version="0.3", features=["axum-view+cookie"] }
|
||||||
|
|
||||||
axum = { version = "0.7", features = ["macros"] }
|
axum-client-ip = "0.7"
|
||||||
axum-extra = { version = "0.9", features = ["cookie", "typed-header"] }
|
axum-extra = { version = "0.10", features = ["cookie", "typed-header"] }
|
||||||
axum-client-ip = "0.6"
|
axum = { version = "0.8", features = ["macros"] }
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
governor = "0.6"
|
env_logger = "0.11"
|
||||||
idna = "1.0"
|
governor = "0.8"
|
||||||
parking_lot = "0.12"
|
|
||||||
regex = "1.10"
|
|
||||||
serde = { version = "1", features = ["derive","rc"] }
|
|
||||||
tokio = { version = "1", features = ["macros","signal"] }
|
|
||||||
tera = "1"
|
|
||||||
toml = "0.8"
|
|
||||||
tower = "0.4"
|
|
||||||
tower-http = { version = "0.5", features = ["fs"] }
|
|
||||||
hickory-proto = "0.24"
|
hickory-proto = "0.24"
|
||||||
hickory-resolver = { version = "0.24", features = ["dns-over-rustls","dns-over-https","dns-over-quic","native-certs"] }
|
hickory-resolver = { version = "0.24", features = ["dns-over-rustls","dns-over-https","dns-over-quic","native-certs"] }
|
||||||
|
http = "1.2"
|
||||||
|
idna = "1.0"
|
||||||
|
log = "0.4"
|
||||||
maxminddb = "0.24"
|
maxminddb = "0.24"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
http = "1.1"
|
parking_lot = "0.12"
|
||||||
|
regex = "1.11"
|
||||||
|
serde = { version = "1", features = ["derive","rc"] }
|
||||||
|
tera = "1"
|
||||||
|
tokio = { version = "1", features = ["macros","signal"] }
|
||||||
|
toml = "0.8"
|
||||||
|
tower = "0.5"
|
||||||
|
tower-http = { version = "0.6", features = ["fs"] }
|
||||||
|
@ -98,6 +98,10 @@ Most noably you can disable reverse dns lookups, hide domains with given suffixe
|
|||||||
`echoip-slatecave` only exposes an unencrypted http interface to keep the service itself simple.
|
`echoip-slatecave` only exposes an unencrypted http interface to keep the service itself simple.
|
||||||
For a public service you should use a reverse proxy like Caddy, apache2 or nginx and configure the `ip_header` option, see the echoip_config.toml file. Usually the preconfigured `RightmostXForwardedFor` is the correct one, but please doublecheck it matches your servers configuration, it should fail by simply not working, but no guarantees given.
|
For a public service you should use a reverse proxy like Caddy, apache2 or nginx and configure the `ip_header` option, see the echoip_config.toml file. Usually the preconfigured `RightmostXForwardedFor` is the correct one, but please doublecheck it matches your servers configuration, it should fail by simply not working, but no guarantees given.
|
||||||
|
|
||||||
|
Consider hiding the values of the following in your server logs for increased privacy:
|
||||||
|
* The `query` URL query paramter
|
||||||
|
* All paths subpath to `/ip/` and `/dig/`
|
||||||
|
|
||||||
### Denail of Service
|
### Denail of Service
|
||||||
|
|
||||||
`echoip-slatecave` has some simle ratelimiting built in (see the `[ratelimit]` section in the configuration file) this should help you with too frequest automated requests causung high load.
|
`echoip-slatecave` has some simle ratelimiting built in (see the `[ratelimit]` section in the configuration file) this should help you with too frequest automated requests causung high load.
|
||||||
|
@ -73,14 +73,14 @@ impl Default for DnsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Protocol> for DnsProtocol {
|
impl From<DnsProtocol> for Protocol {
|
||||||
fn into(self) -> Protocol {
|
fn from(value: DnsProtocol) -> Self {
|
||||||
match self {
|
match value {
|
||||||
Self::Udp => Protocol::Udp,
|
DnsProtocol::Udp => Protocol::Udp,
|
||||||
Self::Tcp => Protocol::Tcp,
|
DnsProtocol::Tcp => Protocol::Tcp,
|
||||||
Self::Tls => Protocol::Tls,
|
DnsProtocol::Tls => Protocol::Tls,
|
||||||
Self::Https => Protocol::Https,
|
DnsProtocol::Https => Protocol::Https,
|
||||||
Self::Quic => Protocol::Quic,
|
DnsProtocol::Quic => Protocol::Quic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
src/geoip.rs
49
src/geoip.rs
@ -3,9 +3,10 @@
|
|||||||
* that provides the results ready for templating.
|
* that provides the results ready for templating.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use maxminddb;
|
use log::{debug,info,warn,error};
|
||||||
use maxminddb::geoip2;
|
use maxminddb::geoip2;
|
||||||
|
|
||||||
|
use maxminddb::MaxMindDBError::AddressNotFoundError;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@ -55,7 +56,7 @@ pub struct MMDBCarrier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait QueryLocation {
|
pub trait QueryLocation {
|
||||||
fn query_location_for_ip(&self, address: &IpAddr, laguages: &Vec<&String>) -> Option<LocationResult>;
|
fn query_location_for_ip(&self, address: &IpAddr, laguages: &[&str]) -> Option<LocationResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait QueryAsn {
|
pub trait QueryAsn {
|
||||||
@ -66,12 +67,12 @@ pub trait QueryAsn {
|
|||||||
|
|
||||||
pub fn extract_localized_name(
|
pub fn extract_localized_name(
|
||||||
names: &Option<BTreeMap<&str, &str>>,
|
names: &Option<BTreeMap<&str, &str>>,
|
||||||
languages: &Vec<&String>)
|
languages: &[&str])
|
||||||
-> Option<String> {
|
-> Option<String> {
|
||||||
match names {
|
match names {
|
||||||
Some(names) => {
|
Some(names) => {
|
||||||
for language in languages {
|
for language in languages {
|
||||||
if let Some(name) = names.get(language.as_str()){
|
if let Some(name) = names.get(language){
|
||||||
return Some(name.to_string())
|
return Some(name.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ names: &Option<BTreeMap<&str, &str>>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn geoip2_city_to_named_location(item: geoip2::city::City, languages: &Vec<&String>) -> NamedLocation {
|
pub fn geoip2_city_to_named_location(item: geoip2::city::City, languages: &[&str]) -> NamedLocation {
|
||||||
NamedLocation {
|
NamedLocation {
|
||||||
iso_code: None,
|
iso_code: None,
|
||||||
geoname_id: item.geoname_id,
|
geoname_id: item.geoname_id,
|
||||||
@ -89,7 +90,7 @@ pub fn geoip2_city_to_named_location(item: geoip2::city::City, languages: &Vec<&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn geoip2_continent_to_named_location(item: geoip2::country::Continent, languages: &Vec<&String>) -> NamedLocation {
|
pub fn geoip2_continent_to_named_location(item: geoip2::country::Continent, languages: &[&str]) -> NamedLocation {
|
||||||
NamedLocation {
|
NamedLocation {
|
||||||
iso_code: item.code.map(ToString::to_string),
|
iso_code: item.code.map(ToString::to_string),
|
||||||
geoname_id: item.geoname_id,
|
geoname_id: item.geoname_id,
|
||||||
@ -97,7 +98,7 @@ pub fn geoip2_continent_to_named_location(item: geoip2::country::Continent, lang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn geoip2_country_to_named_location(item: geoip2::country::Country, languages: &Vec<&String>) -> NamedLocation {
|
pub fn geoip2_country_to_named_location(item: geoip2::country::Country, languages: &[&str]) -> NamedLocation {
|
||||||
NamedLocation {
|
NamedLocation {
|
||||||
iso_code: item.iso_code.map(ToString::to_string),
|
iso_code: item.iso_code.map(ToString::to_string),
|
||||||
geoname_id: item.geoname_id,
|
geoname_id: item.geoname_id,
|
||||||
@ -105,7 +106,7 @@ pub fn geoip2_country_to_named_location(item: geoip2::country::Country, language
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn geoip2_represented_country_to_named_location(item: geoip2::country::RepresentedCountry, languages: &Vec<&String>) -> NamedLocation {
|
pub fn geoip2_represented_country_to_named_location(item: geoip2::country::RepresentedCountry, languages: &[&str]) -> NamedLocation {
|
||||||
NamedLocation {
|
NamedLocation {
|
||||||
iso_code: item.iso_code.map(ToString::to_string),
|
iso_code: item.iso_code.map(ToString::to_string),
|
||||||
geoname_id: item.geoname_id,
|
geoname_id: item.geoname_id,
|
||||||
@ -113,7 +114,7 @@ pub fn geoip2_represented_country_to_named_location(item: geoip2::country::Repre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn geoip2_subdivision_to_named_location(item: geoip2::city::Subdivision, languages: &Vec<&String>) -> NamedLocation {
|
pub fn geoip2_subdivision_to_named_location(item: geoip2::city::Subdivision, languages: &[&str]) -> NamedLocation {
|
||||||
NamedLocation {
|
NamedLocation {
|
||||||
iso_code: item.iso_code.map(ToString::to_string),
|
iso_code: item.iso_code.map(ToString::to_string),
|
||||||
geoname_id: item.geoname_id,
|
geoname_id: item.geoname_id,
|
||||||
@ -135,9 +136,15 @@ impl QueryAsn for MMDBCarrier {
|
|||||||
name: res.autonomous_system_organization.map(ToString::to_string),
|
name: res.autonomous_system_organization.map(ToString::to_string),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Err(AddressNotFoundError(_)) => {
|
||||||
|
// Log to the debug channel.
|
||||||
|
// This isn't severe, and shouldn't be logged in production.
|
||||||
|
debug!("ASN not found in database for {address}.");
|
||||||
|
None
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while looking up ASN for {address}: {e}");
|
error!("Error while looking up ASN for {address}: {e}");
|
||||||
Default::default()
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -147,7 +154,7 @@ impl QueryAsn for MMDBCarrier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl QueryLocation for MMDBCarrier {
|
impl QueryLocation for MMDBCarrier {
|
||||||
fn query_location_for_ip(&self, address: &IpAddr, languages: &Vec<&String>) -> Option<LocationResult> {
|
fn query_location_for_ip(&self, address: &IpAddr, languages: &[&str]) -> Option<LocationResult> {
|
||||||
let mmdb = self.mmdb.read();
|
let mmdb = self.mmdb.read();
|
||||||
match &*mmdb {
|
match &*mmdb {
|
||||||
Some(mmdb) => {
|
Some(mmdb) => {
|
||||||
@ -203,9 +210,15 @@ impl QueryLocation for MMDBCarrier {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Err(AddressNotFoundError(_)) => {
|
||||||
|
// Log to the debug channel.
|
||||||
|
// This isn't severe, and shouldn't be logged in production.
|
||||||
|
debug!("IP location not found in database for {address}");
|
||||||
|
None
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while looking up ASN for {address}: {e}");
|
error!("Error while looking up IP location for {address}: {e}");
|
||||||
Default::default()
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -232,7 +245,7 @@ impl MMDBCarrier {
|
|||||||
|
|
||||||
pub fn load_database_from_path(&self, path: &Path) -> Result<(),maxminddb::MaxMindDBError> {
|
pub fn load_database_from_path(&self, path: &Path) -> Result<(),maxminddb::MaxMindDBError> {
|
||||||
let mut mmdb = self.mmdb.write();
|
let mut mmdb = self.mmdb.write();
|
||||||
println!("Loading {} from '{}' ...", &self.name, path.display());
|
info!("Loading {} from '{}' ...", &self.name, path.display());
|
||||||
match maxminddb::Reader::open_readfile(path) {
|
match maxminddb::Reader::open_readfile(path) {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
let wording = if mmdb.is_some() {
|
let wording = if mmdb.is_some() {
|
||||||
@ -241,13 +254,13 @@ impl MMDBCarrier {
|
|||||||
"Loaded new"
|
"Loaded new"
|
||||||
};
|
};
|
||||||
*mmdb = Some(reader);
|
*mmdb = Some(reader);
|
||||||
println!("{} {} with new one.", wording, &self.name);
|
info!("{} {} with new one.", wording, &self.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while reading {}: {}", &self.name, &e);
|
error!("Error while reading {}: {}", &self.name, &e);
|
||||||
if mmdb.is_some() {
|
if mmdb.is_some() {
|
||||||
println!("Not replacing old database.");
|
warn!("Not replacing old database.");
|
||||||
}
|
}
|
||||||
Err(e)
|
Err(e)
|
||||||
},
|
},
|
||||||
|
12
src/idna.rs
12
src/idna.rs
@ -14,7 +14,7 @@ pub enum NameType {
|
|||||||
#[default]
|
#[default]
|
||||||
Ascii,
|
Ascii,
|
||||||
Unicode,
|
Unicode,
|
||||||
IDN,
|
Idn,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note, that the
|
// Note, that the
|
||||||
@ -32,8 +32,8 @@ pub struct IdnaName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IdnaName {
|
impl IdnaName {
|
||||||
pub fn from_string(s: &String) -> Self {
|
pub fn from_str(s: &str) -> Self {
|
||||||
if s == "" {
|
if s.is_empty() {
|
||||||
return Default::default();
|
return Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,17 +41,17 @@ impl IdnaName {
|
|||||||
let unicode: String;
|
let unicode: String;
|
||||||
let decoder_error;
|
let decoder_error;
|
||||||
if s.starts_with("xn--") && s.is_ascii() {
|
if s.starts_with("xn--") && s.is_ascii() {
|
||||||
original_was = NameType::IDN;
|
original_was = NameType::Idn;
|
||||||
let (uc, ures) = idna::domain_to_unicode(s);
|
let (uc, ures) = idna::domain_to_unicode(s);
|
||||||
unicode = uc;
|
unicode = uc;
|
||||||
decoder_error = ures.map_or_else(|e| Some(e.to_string()), |_| None);
|
decoder_error = ures.map_or_else(|e| Some(e.to_string()), |_| None);
|
||||||
} else {
|
} else {
|
||||||
unicode = s.clone();
|
unicode = s.to_owned();
|
||||||
decoder_error = None;
|
decoder_error = None;
|
||||||
};
|
};
|
||||||
let (idn, encoder_error) = match idna::domain_to_ascii(s) {
|
let (idn, encoder_error) = match idna::domain_to_ascii(s) {
|
||||||
Ok(idn) => {
|
Ok(idn) => {
|
||||||
if &idn != s || original_was == NameType::IDN {
|
if idn != s || original_was == NameType::Idn {
|
||||||
(Some(idn), None)
|
(Some(idn), None)
|
||||||
} else {
|
} else {
|
||||||
original_was = NameType::Ascii;
|
original_was = NameType::Ascii;
|
||||||
|
@ -111,6 +111,7 @@ impl AddressInfo {
|
|||||||
scope: address_scope
|
scope: address_scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
144
src/main.rs
144
src/main.rs
@ -1,3 +1,7 @@
|
|||||||
|
|
||||||
|
#![allow(clippy::redundant_field_names)]
|
||||||
|
#![allow(clippy::needless_return)]
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{
|
extract::{
|
||||||
@ -17,12 +21,14 @@ use axum_client_ip::SecureClientIp;
|
|||||||
use axum_extra::headers;
|
use axum_extra::headers;
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use regex::Regex;
|
use env_logger::Env;
|
||||||
use serde::{Deserialize,Serialize};
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
use hickory_resolver::Name;
|
use hickory_resolver::Name;
|
||||||
use hickory_resolver::TokioAsyncResolver;
|
use hickory_resolver::TokioAsyncResolver;
|
||||||
|
use log::{info,warn,error};
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize,Serialize};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
|
||||||
use tokio::signal::unix::{
|
use tokio::signal::unix::{
|
||||||
signal,
|
signal,
|
||||||
@ -59,7 +65,7 @@ use crate::idna::IdnaName;
|
|||||||
use crate::simple_dns::DnsLookupResult;
|
use crate::simple_dns::DnsLookupResult;
|
||||||
use crate::settings::*;
|
use crate::settings::*;
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use crate::ipinfo::{AddressCast,AddressInfo,AddressScope};
|
use crate::ipinfo::{AddressInfo,AddressScope};
|
||||||
|
|
||||||
type TemplatingEngine = HumusEngine<View,QuerySettings,ResponseFormat>;
|
type TemplatingEngine = HumusEngine<View,QuerySettings,ResponseFormat>;
|
||||||
|
|
||||||
@ -131,7 +137,7 @@ struct CliArgs {
|
|||||||
static_location: Option<String>,
|
static_location: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_domain_hidden_list(domain: &String, hidden_list: &Vec<String>) -> bool {
|
fn match_domain_hidden_list(domain: &str, hidden_list: &Vec<String>) -> bool {
|
||||||
let name = domain.trim_end_matches(".");
|
let name = domain.trim_end_matches(".");
|
||||||
for suffix in hidden_list {
|
for suffix in hidden_list {
|
||||||
if name.ends_with(suffix) {
|
if name.ends_with(suffix) {
|
||||||
@ -143,6 +149,10 @@ fn match_domain_hidden_list(domain: &String, hidden_list: &Vec<String>) -> bool
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
||||||
|
// Initalize logger:
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
|
||||||
// Parse Command line arguments
|
// Parse Command line arguments
|
||||||
let cli_args = CliArgs::parse();
|
let cli_args = CliArgs::parse();
|
||||||
|
|
||||||
@ -152,9 +162,9 @@ async fn main() {
|
|||||||
match read_toml_from_file::<config::EchoIpServiceConfig>(&config_path) {
|
match read_toml_from_file::<config::EchoIpServiceConfig>(&config_path) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Could not read confuration file!");
|
error!("Could not read confuration file!");
|
||||||
println!("{e}");
|
error!("{e}");
|
||||||
println!("Exiting ...");
|
error!("Exiting ...");
|
||||||
::std::process::exit(1);
|
::std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +184,7 @@ async fn main() {
|
|||||||
let templating_engine = match template_loader.load_templates() {
|
let templating_engine = match template_loader.load_templates() {
|
||||||
Ok(t) => t.into(),
|
Ok(t) => t.into(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e}");
|
error!("{e}");
|
||||||
::std::process::exit(1);
|
::std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -183,7 +193,7 @@ async fn main() {
|
|||||||
|
|
||||||
let static_file_directory = template_loader.base_dir()+"/static";
|
let static_file_directory = template_loader.base_dir()+"/static";
|
||||||
|
|
||||||
println!("Static files will be served from: {static_file_directory}");
|
info!("Static files will be served from: {static_file_directory}");
|
||||||
|
|
||||||
// Initalize GeoIP Database
|
// Initalize GeoIP Database
|
||||||
|
|
||||||
@ -202,19 +212,19 @@ async fn main() {
|
|||||||
location_db.reload_database().ok();
|
location_db.reload_database().ok();
|
||||||
|
|
||||||
// Initalize DNS resolver with os defaults
|
// Initalize DNS resolver with os defaults
|
||||||
println!("Initalizing dns resolvers ...");
|
info!("Initalizing dns resolvers ...");
|
||||||
|
|
||||||
let mut dns_resolver_selectables = Vec::<Selectable>::new();
|
let mut dns_resolver_selectables = Vec::<Selectable>::new();
|
||||||
let mut dns_resolver_map: HashMap<Arc<str>,TokioAsyncResolver> = HashMap::new();
|
let mut dns_resolver_map: HashMap<Arc<str>,TokioAsyncResolver> = HashMap::new();
|
||||||
let mut dns_resolver_aliases: HashMap<Arc<str>,Arc<str>> = HashMap::new();
|
let mut dns_resolver_aliases: HashMap<Arc<str>,Arc<str>> = HashMap::new();
|
||||||
|
|
||||||
if config.dns.enable_system_resolver {
|
if config.dns.enable_system_resolver {
|
||||||
println!("Initalizing System resolver ...");
|
info!("Initalizing System resolver ...");
|
||||||
let res = TokioAsyncResolver::tokio_from_system_conf();
|
let res = TokioAsyncResolver::tokio_from_system_conf();
|
||||||
let resolver = match res {
|
let resolver = match res {
|
||||||
Ok(resolver) => resolver,
|
Ok(resolver) => resolver,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while setting up dns resolver: {e}");
|
info!("Error while setting up dns resolver: {e}");
|
||||||
::std::process::exit(1);
|
::std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -228,7 +238,7 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (key, resolver_config) in &config.dns.resolver {
|
for (key, resolver_config) in &config.dns.resolver {
|
||||||
println!("Initalizing {} resolver ...", key);
|
info!("Initalizing {} resolver ...", key);
|
||||||
let resolver = TokioAsyncResolver::tokio(
|
let resolver = TokioAsyncResolver::tokio(
|
||||||
resolver_config.to_hickory_resolver_config(),
|
resolver_config.to_hickory_resolver_config(),
|
||||||
Default::default()
|
Default::default()
|
||||||
@ -259,7 +269,7 @@ async fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dns_resolver_selectables.sort_by(|a,b| b.weight.cmp(&a.weight));
|
dns_resolver_selectables.sort_by(|a,b| b.weight.cmp(&a.weight));
|
||||||
let default_resolver = dns_resolver_selectables.get(0)
|
let default_resolver = dns_resolver_selectables.first()
|
||||||
.map(|s| s.id.clone() )
|
.map(|s| s.id.clone() )
|
||||||
.unwrap_or("none".into());
|
.unwrap_or("none".into());
|
||||||
let derived_config = DerivedConfiguration {
|
let derived_config = DerivedConfiguration {
|
||||||
@ -270,18 +280,18 @@ async fn main() {
|
|||||||
let signal_usr1_handlers_state = shared_state.clone();
|
let signal_usr1_handlers_state = shared_state.clone();
|
||||||
|
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
println!("Trying to register USR1 signal for reloading geoip databases");
|
info!("Trying to register USR1 signal for reloading geoip databases");
|
||||||
let mut signal_stream = match signal(SignalKind::user_defined1()) {
|
let mut signal_stream = match signal(SignalKind::user_defined1()) {
|
||||||
Ok(signal_stream) => signal_stream,
|
Ok(signal_stream) => signal_stream,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error while registring signal handler: {e}");
|
error!("Error while registring signal handler: {e}");
|
||||||
println!("Continuing without ...");
|
warn!("Continuing without geoip reaload signal ...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
if None == signal_stream.recv().await { return; }
|
if signal_stream.recv().await.is_none() { return; }
|
||||||
println!("Received signal USR1, reloading geoip databses!");
|
info!("Received signal USR1, reloading geoip databses!");
|
||||||
signal_usr1_handlers_state.location_db.reload_database().ok();
|
signal_usr1_handlers_state.location_db.reload_database().ok();
|
||||||
signal_usr1_handlers_state.asn_db.reload_database().ok();
|
signal_usr1_handlers_state.asn_db.reload_database().ok();
|
||||||
}
|
}
|
||||||
@ -290,9 +300,9 @@ async fn main() {
|
|||||||
// Initalize axum server
|
// Initalize axum server
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(handle_default_route))
|
.route("/", get(handle_default_route))
|
||||||
.route("/dig/:name", get(handle_dig_route_with_path))
|
.route("/dig/{name}", get(handle_dig_route_with_path))
|
||||||
.route("/ip/:address", get(handle_ip_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/{resolver}", get(handle_dns_resolver_route_with_path))
|
||||||
.route("/dns_resolver", get(handle_dns_resolver_route))
|
.route("/dns_resolver", get(handle_dns_resolver_route))
|
||||||
.route("/ua", get(user_agent_handler))
|
.route("/ua", get(user_agent_handler))
|
||||||
.route("/hi", get(hello_world_handler))
|
.route("/hi", get(hello_world_handler))
|
||||||
@ -300,7 +310,7 @@ async fn main() {
|
|||||||
ServeDir::new(static_file_directory)
|
ServeDir::new(static_file_directory)
|
||||||
.fallback(not_found_handler.with_state(shared_state.clone()))
|
.fallback(not_found_handler.with_state(shared_state.clone()))
|
||||||
)
|
)
|
||||||
.with_state(shared_state)
|
.with_state(shared_state.clone())
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.layer(ip_header.into_extension())
|
.layer(ip_header.into_extension())
|
||||||
@ -309,11 +319,11 @@ async fn main() {
|
|||||||
.layer(middleware::from_fn(ratelimit::rate_limit_middleware))
|
.layer(middleware::from_fn(ratelimit::rate_limit_middleware))
|
||||||
.layer(Extension(config))
|
.layer(Extension(config))
|
||||||
.layer(Extension(derived_config))
|
.layer(Extension(derived_config))
|
||||||
.layer(middleware::from_fn(settings_query_middleware))
|
.layer(middleware::from_fn_with_state(shared_state, settings_query_middleware))
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
println!("Starting Server on {} ...",listen_on);
|
info!("Starting Server on {} ...",listen_on);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(&listen_on).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(&listen_on).await.unwrap();
|
||||||
axum::serve(listener, app.into_make_service_with_connect_info::<std::net::SocketAddr>())
|
axum::serve(listener, app.into_make_service_with_connect_info::<std::net::SocketAddr>())
|
||||||
@ -321,26 +331,38 @@ async fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn settings_query_middleware(
|
async fn settings_query_middleware(
|
||||||
Query(query): Query<SettingsQuery>,
|
Query(query): Query<SettingsQuery>,
|
||||||
|
State(arc_state): State<Arc<ServiceSharedState>>,
|
||||||
Extension(config): Extension<config::EchoIpServiceConfig>,
|
Extension(config): Extension<config::EchoIpServiceConfig>,
|
||||||
Extension(derived_config): Extension<DerivedConfiguration>,
|
Extension(derived_config): Extension<DerivedConfiguration>,
|
||||||
cookie_header: Option<TypedHeader<headers::Cookie>>,
|
cookie_header: Option<TypedHeader<headers::Cookie>>,
|
||||||
user_agent_header: Option<TypedHeader<headers::UserAgent>>,
|
user_agent_header: Option<TypedHeader<headers::UserAgent>>,
|
||||||
mut req: Request<Body>,
|
mut req: Request<Body>,
|
||||||
next: Next
|
next: Next,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
|
let state = Arc::clone(&arc_state);
|
||||||
let mut format = query.format;
|
let mut format = query.format;
|
||||||
let mut dns_resolver_id = derived_config.default_resolver;
|
|
||||||
|
let mut dns_resolver_id = derived_config.default_resolver.clone();
|
||||||
|
let mut test_for_resolver = false;
|
||||||
|
|
||||||
if let Some(resolver_id) = query.dns {
|
if let Some(resolver_id) = query.dns {
|
||||||
dns_resolver_id = resolver_id.into();
|
dns_resolver_id = resolver_id.into();
|
||||||
|
test_for_resolver = true;
|
||||||
} else if let Some(cookie_header) = cookie_header {
|
} else if let Some(cookie_header) = cookie_header {
|
||||||
if let Some(resolver_id) = cookie_header.0.get("dns_resolver") {
|
if let Some(resolver_id) = cookie_header.0.get("dns_resolver") {
|
||||||
dns_resolver_id = resolver_id.into();
|
dns_resolver_id = resolver_id.into();
|
||||||
|
test_for_resolver = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Falls back to the default resolver if an invalid resolver id ws requested.
|
||||||
|
// This may be the case for bookmarked links or old cookies of a resolver was removed.
|
||||||
|
if test_for_resolver && !state.dns_resolvers.contains_key(&dns_resolver_id) {
|
||||||
|
dns_resolver_id = derived_config.default_resolver;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to guess type from user agent
|
// Try to guess type from user agent
|
||||||
if format.is_none() {
|
if format.is_none() {
|
||||||
@ -430,10 +452,8 @@ async fn handle_default_route(
|
|||||||
&state,
|
&state,
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
let user_agent: Option<String> = match user_agent_header {
|
let user_agent: Option<String> = user_agent_header
|
||||||
Some(TypedHeader(user_agent)) => Some(user_agent.to_string()),
|
.map(|TypedHeader(user_agent)| user_agent.to_string());
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
state.templating_engine.render_view(
|
state.templating_engine.render_view(
|
||||||
&settings,
|
&settings,
|
||||||
@ -460,7 +480,7 @@ async fn handle_search_request(
|
|||||||
|
|
||||||
//If someone asked for an asn, give an asn answer
|
//If someone asked for an asn, give an asn answer
|
||||||
if let Some(asn_cap) = ASN_REGEX.captures(&search_query) {
|
if let Some(asn_cap) = ASN_REGEX.captures(&search_query) {
|
||||||
if let Some(asn) = asn_cap.get(1).map_or(None, |m| m.as_str().parse::<u32>().ok()) {
|
if let Some(asn) = asn_cap.get(1).and_then(|m| m.as_str().parse::<u32>().ok()) {
|
||||||
// Render a dummy template that can at least link to other pages
|
// Render a dummy template that can at least link to other pages
|
||||||
let state = Arc::clone(&arc_state);
|
let state = Arc::clone(&arc_state);
|
||||||
return state.templating_engine.render_view(
|
return state.templating_engine.render_view(
|
||||||
@ -565,7 +585,7 @@ async fn handle_ip_request(
|
|||||||
|
|
||||||
async fn get_ip_result(
|
async fn get_ip_result(
|
||||||
address: &IpAddr,
|
address: &IpAddr,
|
||||||
lang: &String,
|
lang: &str,
|
||||||
dns_resolver_name: &Arc<str>,
|
dns_resolver_name: &Arc<str>,
|
||||||
dns_disable_self_lookup: bool,
|
dns_disable_self_lookup: bool,
|
||||||
client_ip: &IpAddr,
|
client_ip: &IpAddr,
|
||||||
@ -574,34 +594,36 @@ async fn get_ip_result(
|
|||||||
|
|
||||||
let mut reverse_dns_disabled_for_privacy = false;
|
let mut reverse_dns_disabled_for_privacy = false;
|
||||||
|
|
||||||
if state.config.dns.allow_reverse_lookup {
|
if state.config.dns.allow_reverse_lookup &&
|
||||||
if address == client_ip && dns_disable_self_lookup {
|
address == client_ip &&
|
||||||
reverse_dns_disabled_for_privacy = true;
|
dns_disable_self_lookup
|
||||||
}
|
{
|
||||||
|
reverse_dns_disabled_for_privacy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ip_info = AddressInfo::new(&address);
|
let ip_info = AddressInfo::new(address);
|
||||||
|
|
||||||
if !(ip_info.scope == AddressScope::Global || ip_info.scope == AddressScope::Shared) || ip_info.cast != AddressCast::Unicast {
|
// Return dummy result if:
|
||||||
if !((ip_info.scope == AddressScope::Private || ip_info.scope == AddressScope::LinkLocal) && state.config.server.allow_private_ip_lookup) {
|
//
|
||||||
return IpResult {
|
// The address falls into a private range and lookup of private addresses is not allowed.
|
||||||
address: *address,
|
if (!state.config.server.allow_private_ip_lookup) && (ip_info.scope == AddressScope::Private || ip_info.scope == AddressScope::LinkLocal) {
|
||||||
hostname: None,
|
return IpResult {
|
||||||
asn: None,
|
address: *address,
|
||||||
location: None,
|
hostname: None,
|
||||||
ip_info: ip_info,
|
asn: None,
|
||||||
used_dns_resolver: None,
|
location: None,
|
||||||
reverse_dns_disabled_for_privacy: reverse_dns_disabled_for_privacy,
|
ip_info: ip_info,
|
||||||
}
|
used_dns_resolver: None,
|
||||||
|
reverse_dns_disabled_for_privacy: reverse_dns_disabled_for_privacy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do reverse lookup
|
// do reverse lookup
|
||||||
let mut hostname: Option<String> = None;
|
let mut hostname: Option<String> = None;
|
||||||
let mut used_dns_resolver: Option<Arc<str>> = None;
|
let mut used_dns_resolver: Option<Arc<str>> = None;
|
||||||
if state.config.dns.allow_reverse_lookup && !reverse_dns_disabled_for_privacy {
|
if state.config.dns.allow_reverse_lookup && !reverse_dns_disabled_for_privacy {
|
||||||
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
|
if let Some(dns_resolver) = &state.dns_resolvers.get(dns_resolver_name) {
|
||||||
hostname = simple_dns::reverse_lookup(&dns_resolver, &address).await;
|
hostname = simple_dns::reverse_lookup(dns_resolver, address).await;
|
||||||
used_dns_resolver = Some(dns_resolver_name.clone());
|
used_dns_resolver = Some(dns_resolver_name.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -612,12 +634,12 @@ async fn get_ip_result(
|
|||||||
// location lookup
|
// location lookup
|
||||||
let location_result = state.location_db.query_location_for_ip(
|
let location_result = state.location_db.query_location_for_ip(
|
||||||
address,
|
address,
|
||||||
&vec![lang, &"en".to_string()]
|
&[lang, "en"]
|
||||||
);
|
);
|
||||||
|
|
||||||
// filter reverse lookup
|
// filter reverse lookup
|
||||||
if let Some(name) = &hostname {
|
if let Some(name) = &hostname {
|
||||||
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
|
if match_domain_hidden_list(name, &state.config.dns.hidden_suffixes) {
|
||||||
hostname = None;
|
hostname = None;
|
||||||
used_dns_resolver = None;
|
used_dns_resolver = None;
|
||||||
}
|
}
|
||||||
@ -666,21 +688,21 @@ async fn handle_dig_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_dig_result(
|
async fn get_dig_result(
|
||||||
dig_query: &String,
|
dig_query: &str,
|
||||||
dns_resolver_name: &Arc<str>,
|
dns_resolver_name: &Arc<str>,
|
||||||
state: &ServiceSharedState,
|
state: &ServiceSharedState,
|
||||||
do_full_lookup: bool,
|
do_full_lookup: bool,
|
||||||
) -> DigResult {
|
) -> DigResult {
|
||||||
let name = &dig_query.trim().trim_end_matches(".").to_string();
|
let name = &dig_query.trim().trim_end_matches(".").to_string();
|
||||||
let idna_name = IdnaName::from_string(&name);
|
let idna_name = IdnaName::from_str(name);
|
||||||
if let Some(dns_resolver) = state.dns_resolvers.get(dns_resolver_name) {
|
if let Some(dns_resolver) = state.dns_resolvers.get(dns_resolver_name) {
|
||||||
if let Ok(domain_name) = Name::from_str_relaxed(name.to_owned()+".") {
|
if let Ok(domain_name) = Name::from_str_relaxed(name.to_owned()+".") {
|
||||||
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
|
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
|
// Try to hide the fact that we didn't do dns resolution at all
|
||||||
// We resolve example.org as basic avoidance of timing sidechannels.
|
// We resolve example.org as basic avoidance of timing sidechannels.
|
||||||
// WARNING: this timing sidechannel avoidance is very crude.
|
// WARNING: this timing sidechannel avoidance is very crude.
|
||||||
simple_dns::lookup(
|
simple_dns::lookup(
|
||||||
&dns_resolver,
|
dns_resolver,
|
||||||
&Name::from_ascii("example.org.").expect("Static Dummy Name"),
|
&Name::from_ascii("example.org.").expect("Static Dummy Name"),
|
||||||
do_full_lookup).await;
|
do_full_lookup).await;
|
||||||
return DigResult {
|
return DigResult {
|
||||||
@ -692,7 +714,7 @@ async fn get_dig_result(
|
|||||||
} else {
|
} else {
|
||||||
return DigResult {
|
return DigResult {
|
||||||
records: simple_dns::lookup(
|
records: simple_dns::lookup(
|
||||||
&dns_resolver,
|
dns_resolver,
|
||||||
&domain_name,
|
&domain_name,
|
||||||
do_full_lookup).await,
|
do_full_lookup).await,
|
||||||
idn: idna_name,
|
idn: idna_name,
|
||||||
|
@ -18,6 +18,7 @@ use governor::{
|
|||||||
RateLimiter,
|
RateLimiter,
|
||||||
state::keyed::DefaultKeyedStateStore,
|
state::keyed::DefaultKeyedStateStore,
|
||||||
};
|
};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
@ -55,10 +56,10 @@ pub async fn rate_limit_middleware(
|
|||||||
if limiter.check_key(&IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)).is_ok() {
|
if limiter.check_key(&IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)).is_ok() {
|
||||||
let oldlen = limiter.len();
|
let oldlen = limiter.len();
|
||||||
if oldlen > 100 {
|
if oldlen > 100 {
|
||||||
println!("Doing limiter cleanup ...");
|
debug!("Doing limiter cleanup ...");
|
||||||
limiter.retain_recent();
|
limiter.retain_recent();
|
||||||
limiter.shrink_to_fit();
|
limiter.shrink_to_fit();
|
||||||
println!("Old limiter store size: {oldlen} New limiter store size: {}", limiter.len());
|
debug!("Old limiter store size: {oldlen} New limiter store size: {}", limiter.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next.run(req).await
|
next.run(req).await
|
||||||
|
@ -17,6 +17,7 @@ use hickory_resolver::{
|
|||||||
Name,
|
Name,
|
||||||
TokioAsyncResolver,
|
TokioAsyncResolver,
|
||||||
};
|
};
|
||||||
|
use log::{warn,error};
|
||||||
|
|
||||||
use tokio::join;
|
use tokio::join;
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ pub async fn reverse_lookup(
|
|||||||
let revese_res = resolver.reverse_lookup(*address);
|
let revese_res = resolver.reverse_lookup(*address);
|
||||||
match revese_res.await {
|
match revese_res.await {
|
||||||
Ok(lookup) => {
|
Ok(lookup) => {
|
||||||
for name in lookup {
|
if let Some(name) = lookup.iter().next() {
|
||||||
return Some(name.to_string())
|
return Some(name.to_string())
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -91,7 +92,7 @@ pub async fn reverse_lookup(
|
|||||||
//Ignore, that just happens …
|
//Ignore, that just happens …
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Reverse lookup on {address} failed: {kind}");
|
error!("Reverse lookup on {address} failed: {kind}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -153,7 +154,9 @@ pub fn add_record_to_lookup_result(result: &mut DnsLookupResult, record: &RData)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => { println!("Tried to add an unkown DNS record to results: {record}"); },
|
_ => {
|
||||||
|
warn!("Tried to add an unkown DNS record to results: {record}");
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,18 +194,18 @@ pub fn integrate_lookup_result(dig_result: &mut DnsLookupResult, lookup_result:
|
|||||||
ResolveErrorKind::Io(..) |
|
ResolveErrorKind::Io(..) |
|
||||||
ResolveErrorKind::Proto(..) => {
|
ResolveErrorKind::Proto(..) => {
|
||||||
dig_result.other_error = true;
|
dig_result.other_error = true;
|
||||||
println!("There was an error while doing a DNS Lookup: {e}");
|
error!("There was an error while doing a DNS Lookup: {e}");
|
||||||
},
|
},
|
||||||
ResolveErrorKind::Timeout => {
|
ResolveErrorKind::Timeout => {
|
||||||
dig_result.timeout = true;
|
dig_result.timeout = true;
|
||||||
println!("There was a timeout while doing a DNS Lookup.");
|
warn!("There was a timeout while doing a DNS Lookup.");
|
||||||
},
|
},
|
||||||
ResolveErrorKind::NoRecordsFound{response_code, ..} => {
|
ResolveErrorKind::NoRecordsFound{response_code, ..} => {
|
||||||
match response_code {
|
match response_code {
|
||||||
ResponseCode::NXDomain => dig_result.nxdomain = true,
|
ResponseCode::NXDomain => dig_result.nxdomain = true,
|
||||||
ResponseCode::NoError => {},
|
ResponseCode::NoError => {},
|
||||||
_ => {
|
_ => {
|
||||||
println!("The DNS Server returned an error while doing a DNS Lookup: {response_code}");
|
error!("The DNS Server returned an error while doing a DNS Lookup: {response_code}");
|
||||||
dig_result.dns_error = true;
|
dig_result.dns_error = true;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user