Finish verion 0.1.0
This commit is contained in:
9
crates/wtfnet-geoip/Cargo.toml
Normal file
9
crates/wtfnet-geoip/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "wtfnet-geoip"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
maxminddb = "0.24"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wtfnet-core = { path = "../wtfnet-core" }
|
||||
98
crates/wtfnet-geoip/src/lib.rs
Normal file
98
crates/wtfnet-geoip/src/lib.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
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<CountryInfo>,
|
||||
pub asn: Option<AsnInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CountryInfo {
|
||||
pub iso_code: Option<String>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AsnInfo {
|
||||
pub number: Option<u32>,
|
||||
pub organization: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GeoIpStatus {
|
||||
pub country_db: Option<String>,
|
||||
pub asn_db: Option<String>,
|
||||
pub country_loaded: bool,
|
||||
pub asn_loaded: bool,
|
||||
}
|
||||
|
||||
pub struct GeoIpService {
|
||||
country_db: Option<(Reader<Vec<u8>>, PathBuf)>,
|
||||
asn_db: Option<(Reader<Vec<u8>>, PathBuf)>,
|
||||
}
|
||||
|
||||
impl GeoIpService {
|
||||
pub fn new(country_path: Option<PathBuf>, asn_path: Option<PathBuf>) -> 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<Reader<Vec<u8>>> {
|
||||
let bytes = std::fs::read(path).ok()?;
|
||||
Reader::from_source(bytes).ok()
|
||||
}
|
||||
|
||||
fn lookup_country(db: &Reader<Vec<u8>>, ip: IpAddr) -> Option<CountryInfo> {
|
||||
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<Vec<u8>>, ip: IpAddr) -> Option<AsnInfo> {
|
||||
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()),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user