use maxminddb::geoip2; use maxminddb::Reader; use serde::{Deserialize, Serialize}; use std::net::IpAddr; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GeoIpRecord { pub ip: String, pub country: Option, pub asn: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CountryInfo { pub iso_code: Option, pub name: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AsnInfo { pub number: Option, pub organization: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GeoIpStatus { pub country_db: Option, pub asn_db: Option, pub country_loaded: bool, pub asn_loaded: bool, } pub struct GeoIpService { country_db: Option<(Reader>, PathBuf)>, asn_db: Option<(Reader>, PathBuf)>, } impl GeoIpService { pub fn new(country_path: Option, asn_path: Option) -> Self { let country_db = country_path .as_ref() .and_then(|path| load_db(path).map(|db| (db, path.clone()))); let asn_db = asn_path .as_ref() .and_then(|path| load_db(path).map(|db| (db, path.clone()))); Self { country_db, asn_db } } pub fn status(&self) -> GeoIpStatus { GeoIpStatus { country_db: self.country_db.as_ref().map(|(_, path)| path.display().to_string()), asn_db: self.asn_db.as_ref().map(|(_, path)| path.display().to_string()), country_loaded: self.country_db.is_some(), asn_loaded: self.asn_db.is_some(), } } pub fn lookup(&self, ip: IpAddr) -> GeoIpRecord { let country = self .country_db .as_ref() .and_then(|(db, _)| lookup_country(db, ip)); let asn = self.asn_db.as_ref().and_then(|(db, _)| lookup_asn(db, ip)); GeoIpRecord { ip: ip.to_string(), country, asn, } } } fn load_db(path: &Path) -> Option>> { let bytes = std::fs::read(path).ok()?; Reader::from_source(bytes).ok() } fn lookup_country(db: &Reader>, ip: IpAddr) -> Option { let data: geoip2::Country = db.lookup(ip).ok()?; let country = data.country?; Some(CountryInfo { iso_code: country.iso_code.map(|value| value.to_string()), name: country .names .as_ref() .and_then(|names| names.get("en").map(|value| value.to_string())), }) } fn lookup_asn(db: &Reader>, ip: IpAddr) -> Option { let data: geoip2::Asn = db.lookup(ip).ok()?; Some(AsnInfo { number: data.autonomous_system_number.map(|value| value as u32), organization: data .autonomous_system_organization .map(|value| value.to_string()), }) }