use itertools::Itertools; use serde::Serialize; use std::{net::Ipv4Addr, sync::Arc}; use warp::{http::StatusCode, reply, Filter}; use serde_json::json; const IP2ASN_DATA: &str = include_str!("../data/ip2asn-v4.tsv"); #[derive(Serialize)] struct RangeRecord { #[serde(skip)] ip_start: Ipv4Addr, #[serde(skip)] ip_end: Ipv4Addr, asn: u64, country_code: &'static str, asn_description: &'static str, } fn parse_ranges() -> Vec { let mut ranges = Vec::with_capacity(IP2ASN_DATA.lines().count()); IP2ASN_DATA .lines() .filter(|s| !s.is_empty()) .map(|l| { l.splitn(5, '\t') .next_tuple::<(&str, &str, &str, &str, &str)>() .unwrap() }) .for_each(|(ip_start, ip_end, asn, country_code, asn_description)| { ranges.push(RangeRecord { ip_start: ip_start.parse().unwrap(), ip_end: ip_end.parse().unwrap(), asn: asn.parse().unwrap(), country_code, asn_description, }) }); ranges } type RangeData = Arc>; fn with_ranges( ranges: RangeData, ) -> impl Filter + Clone { warp::any().map(move || ranges.clone()) } fn get_range_data(ip: String, ranges: RangeData) -> impl warp::Reply { if let Ok(ipv4) = ip.parse::() { for range in ranges.iter() { if (range.ip_start..=range.ip_end).contains(&ipv4) { return reply::with_status(reply::json(&range), StatusCode::OK); } } reply::with_status( reply::json(&json!({ "error": "range not found" })), StatusCode::NOT_FOUND, ) } else { reply::with_status( reply::json(&json!({ "error": "invalid ipv4 address" })), StatusCode::BAD_REQUEST, ) } } #[tokio::main] async fn main() { eprintln!("[i] Parsing IP ASN data..."); let ranges: RangeData = Arc::new(parse_ranges()); eprintln!("[i] Done!"); let get_ip_data = warp::path!(String) .and(with_ranges(ranges.clone())) .map(get_range_data); println!("\nListening on http://127.0.0.1:8000 ..."); warp::serve(get_ip_data).bind(([127, 0, 0, 1], 8000)).await; }