mirror of
https://codeberg.org/slatian/service.echoip-slatecave.git
synced 2024-12-25 19:28:21 +01:00
Write some major geolocation shuffling around code
This commit is contained in:
parent
0193c24385
commit
2fb2385004
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/mmdb
|
||||||
|
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -646,6 +646,16 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
|
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maxminddb"
|
||||||
|
version = "0.17.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d13fa57adcc4f3aca91e511b3cdaa58ed8cbcbf97f20e342a11218c76e127f51"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -959,6 +969,7 @@ name = "rust-web-thingy"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"maxminddb",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"tera",
|
"tera",
|
||||||
|
@ -12,3 +12,4 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tera = "1"
|
tera = "1"
|
||||||
trust-dns-resolver = "0.22"
|
trust-dns-resolver = "0.22"
|
||||||
|
maxminddb = "0.17"
|
||||||
|
200
src/geoip.rs
Normal file
200
src/geoip.rs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* This module provides an abstraction over the maxmind geoip databases
|
||||||
|
* that provides the results ready for templating.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use maxminddb;
|
||||||
|
use maxminddb::geoip2;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
|
||||||
|
/* Datatypes */
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub enum IsTheNameOf {
|
||||||
|
Continent,
|
||||||
|
Country,
|
||||||
|
Subdivision,
|
||||||
|
City,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Default)]
|
||||||
|
pub struct NamedLocation {
|
||||||
|
iso_code: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
geoname_id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Default)]
|
||||||
|
pub struct LocationCoordinates {
|
||||||
|
latitude: f64,
|
||||||
|
logtitude: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Default)]
|
||||||
|
pub struct LocationResult {
|
||||||
|
continent: Option<NamedLocation>,
|
||||||
|
country: Option<NamedLocation>,
|
||||||
|
registered_country: Option<NamedLocation>,
|
||||||
|
represented_country: Option<NamedLocation>,
|
||||||
|
subdivisions: Option<Vec<NamedLocation>>,
|
||||||
|
city: Option<NamedLocation>,
|
||||||
|
|
||||||
|
postal_code: Option<String>,
|
||||||
|
time_zone: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Default)]
|
||||||
|
pub struct AsnResult {
|
||||||
|
asn: Option<u32>,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MMDBCarrier {
|
||||||
|
mmdb: Option<maxminddb::Reader<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait QueryLocation {
|
||||||
|
fn query_location_for_ip(&self, address: IpAddr, laguages: &Vec<String>) -> Option<LocationResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait QueryAsn {
|
||||||
|
fn query_asn_for_ip(&self, address: IpAddr) -> Option<AsnResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converters */
|
||||||
|
|
||||||
|
pub fn extract_localized_name(
|
||||||
|
names: &Option<BTreeMap<&str, &str>>,
|
||||||
|
languages: &Vec<String>)
|
||||||
|
-> Option<String> {
|
||||||
|
match names {
|
||||||
|
Some(names) => {
|
||||||
|
for language in languages {
|
||||||
|
if let Some(name) = names.get(language.as_str()){
|
||||||
|
return Some(name.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn geoip2_city_to_named_location(item: geoip2::model::City, languages: &Vec<String>) -> NamedLocation {
|
||||||
|
NamedLocation {
|
||||||
|
iso_code: None,
|
||||||
|
geoname_id: item.geoname_id,
|
||||||
|
name: extract_localized_name(&item.names, languages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
name: extract_localized_name(&item.names, languages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
name: extract_localized_name(&item.names, languages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
name: extract_localized_name(&item.names, languages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
name: extract_localized_name(&item.names, languages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation */
|
||||||
|
|
||||||
|
impl QueryAsn for MMDBCarrier {
|
||||||
|
fn query_asn_for_ip(&self, address: IpAddr) -> Option<AsnResult> {
|
||||||
|
match self.mmdb {
|
||||||
|
Some(mmdb) => {
|
||||||
|
match mmdb.lookup::<geoip2::Asn>(address) {
|
||||||
|
Ok(res) => {
|
||||||
|
Some(AsnResult {
|
||||||
|
asn: res.autonomous_system_number,
|
||||||
|
name: res.autonomous_system_organization.map(ToString::to_string),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error while looking up ASN for {address}: {e}");
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryLocation for MMDBCarrier {
|
||||||
|
fn query_location_for_ip(&self, address: IpAddr, languages: &Vec<String>) -> Option<LocationResult> {
|
||||||
|
match self.mmdb {
|
||||||
|
Some(mmdb) => {
|
||||||
|
match mmdb.lookup::<geoip2::City>(address) {
|
||||||
|
Ok(res) => {
|
||||||
|
Some(LocationResult {
|
||||||
|
continent:
|
||||||
|
res.continent.map(|c| geoip2_continent_to_named_location(c, languages)),
|
||||||
|
country:
|
||||||
|
res.country.map(|c| geoip2_country_to_named_location(c, languages)),
|
||||||
|
registered_country:
|
||||||
|
res.registered_country.map(|c| geoip2_country_to_named_location(c, languages)),
|
||||||
|
represented_country:
|
||||||
|
res.represented_country.map(|c| geoip2_represented_country_to_named_location(c, languages)),
|
||||||
|
city:
|
||||||
|
res.city.map(|c| geoip2_city_to_named_location(c, languages)),
|
||||||
|
|
||||||
|
subdivisions: match res.subdivisions {
|
||||||
|
Some(sds) => {
|
||||||
|
let subdivisions = Vec::new();
|
||||||
|
subdivisions.reserve_exact(sds.len());
|
||||||
|
for sd in sds {
|
||||||
|
subdivisions.push(geoip2_subdivision_to_named_location(sd, languages));
|
||||||
|
}
|
||||||
|
Some(subdivisions)
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
postal_code: match res.postal {
|
||||||
|
Some(p) => p.code.map(ToString::to_string),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
time_zone: match res.location {
|
||||||
|
Some(loc) => loc.time_zone.map(ToString::to_string),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error while looking up ASN for {address}: {e}");
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
69
src/main.rs
69
src/main.rs
@ -5,6 +5,8 @@ use axum::{
|
|||||||
Router,
|
Router,
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
|
use maxminddb;
|
||||||
|
use maxminddb::geoip2;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use trust_dns_resolver::{
|
use trust_dns_resolver::{
|
||||||
TokioAsyncResolver,
|
TokioAsyncResolver,
|
||||||
@ -15,8 +17,13 @@ use trust_dns_resolver::{
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
||||||
mod simple_dns;
|
mod simple_dns;
|
||||||
mod templating_engine;
|
mod templating_engine;
|
||||||
|
mod geoip;
|
||||||
|
|
||||||
|
use geoip::AsnResult;
|
||||||
|
use geoip::LocationResult;
|
||||||
|
|
||||||
use crate::templating_engine::View;
|
use crate::templating_engine::View;
|
||||||
use crate::templating_engine::ResponseFormat;
|
use crate::templating_engine::ResponseFormat;
|
||||||
@ -36,11 +43,14 @@ pub struct DigQuery {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct IpResult {
|
pub struct IpResult {
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
|
asn: Option<AsnResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServiceSharedState {
|
struct ServiceSharedState {
|
||||||
templating_engine: templating_engine::Engine,
|
templating_engine: templating_engine::Engine,
|
||||||
dns_resolver: TokioAsyncResolver,
|
dns_resolver: TokioAsyncResolver,
|
||||||
|
mmdb_asn: Option<maxminddb::Reader<Vec<u8>>>,
|
||||||
|
mmdb_location: Option<maxminddb::Reader<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -56,6 +66,23 @@ async fn main() {
|
|||||||
::std::process::exit(1);
|
::std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initalize GeoIP Database
|
||||||
|
|
||||||
|
println!("Opening GeoIP ASN Databse ...");
|
||||||
|
let mmdb_asn = maxminddb::Reader::open_readfile("mmdb/GeoLite2-ASN.mmdb");
|
||||||
|
match mmdb_asn {
|
||||||
|
Ok(_) => { /* NOP */ },
|
||||||
|
Err(ref e) => println!("Error while opening GeoIP ASN Databse: {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Opening GeoIP Location Databse ...");
|
||||||
|
let mmdb_location = maxminddb::Reader::open_readfile("mmdb/GeoLite2-City.mmdb");
|
||||||
|
match mmdb_location {
|
||||||
|
Ok(_) => { /* NOP */ },
|
||||||
|
Err(ref e) => println!("Error while opening GeoIP Location Databse: {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initalize DNS resolver with os defaults
|
// Initalize DNS resolver with os defaults
|
||||||
println!("Initalizing dns resolver ...");
|
println!("Initalizing dns resolver ...");
|
||||||
@ -74,6 +101,8 @@ async fn main() {
|
|||||||
tera: tera,
|
tera: tera,
|
||||||
},
|
},
|
||||||
dns_resolver: dns_resolver,
|
dns_resolver: dns_resolver,
|
||||||
|
mmdb_asn: mmdb_asn.ok(),
|
||||||
|
mmdb_location: mmdb_location.ok(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initalize axum server
|
// Initalize axum server
|
||||||
@ -115,9 +144,47 @@ async fn handle_default_route(
|
|||||||
|
|
||||||
// do reverse lookup
|
// do reverse lookup
|
||||||
let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address);
|
let hostname = simple_dns::reverse_lookup(&state.dns_resolver, &address);
|
||||||
|
|
||||||
|
let asn_result = match &state.mmdb_asn {
|
||||||
|
Some(mmdb_asn) => {
|
||||||
|
match mmdb_asn.lookup::<geoip2::Asn>(address) {
|
||||||
|
Ok(res) => {
|
||||||
|
Some(AsnResult {
|
||||||
|
asn: res.autonomous_system_number,
|
||||||
|
name: res.autonomous_system_organization.map(ToString::to_string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error while looking up ASN for {address}: {e}");
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let location_result = match &state.mmdb_location {
|
||||||
|
Some(mmdb_location) => {
|
||||||
|
match mmdb_location.lookup::<geoip2::City>(address) {
|
||||||
|
Ok(res) => {
|
||||||
|
Some(AsnResult {
|
||||||
|
asn: res.autonomous_system_number,
|
||||||
|
name: res.autonomous_system_organization.map(ToString::to_string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error while looking up ASN for {address}: {e}");
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
let result = IpResult{
|
let result = IpResult{
|
||||||
hostname: hostname.await,
|
hostname: hostname.await,
|
||||||
|
asn: asn_result,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.templating_engine.render_view(
|
state.templating_engine.render_view(
|
||||||
@ -136,7 +203,7 @@ async fn handle_dig_route(
|
|||||||
let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml);
|
let format = dig_query.format.unwrap_or(ResponseFormat::TextHtml);
|
||||||
|
|
||||||
let dig_result = simple_dns::lookup(&state.dns_resolver, name, true).await;
|
let dig_result = simple_dns::lookup(&state.dns_resolver, name, true).await;
|
||||||
|
|
||||||
state.templating_engine.render_view(
|
state.templating_engine.render_view(
|
||||||
format,
|
format,
|
||||||
View::Dig{ query: dig_query, result: dig_result}
|
View::Dig{ query: dig_query, result: dig_result}
|
||||||
|
@ -9,5 +9,8 @@
|
|||||||
{% if hostname %}
|
{% if hostname %}
|
||||||
<p>Hostname: <b>{{data.result.hostname}}</b></p>
|
<p>Hostname: <b>{{data.result.hostname}}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if data.result.asn %}
|
||||||
|
<p>This IP is part of <b>AS{{ data.result.asn.asn }}</b>, which belongs to: <i>{{ data.result.asn.name }}</i></p>
|
||||||
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user