Finish verion 0.1.0

This commit is contained in:
DaZuo0122
2026-01-16 13:27:07 +08:00
parent 240107e00f
commit b63bcd405b
17 changed files with 4788 additions and 26 deletions

View 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" }

View 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()),
})
}