Compare commits
No commits in common. "main" and "33674eec78a012fadd66a8c447880f3a317e5adc" have entirely different histories.
main
...
33674eec78
|
@ -1,12 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.rs]
|
|
||||||
indent_size = 4
|
|
|
@ -75,12 +75,6 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -153,9 +147,6 @@ dependencies = [
|
||||||
name = "geoip-api"
|
name = "geoip-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
@ -318,15 +309,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -648,20 +630,6 @@ name = "serde"
|
||||||
version = "1.0.132"
|
version = "1.0.132"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.132"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
|
|
@ -5,8 +5,5 @@ edition = "2021"
|
||||||
authors = ["charlotte ✨ <charlotte@lavender.software>"]
|
authors = ["charlotte ✨ <charlotte@lavender.software>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
itertools = "0.10.3"
|
|
||||||
serde = { version = "1.0.132", features = ["derive"] }
|
|
||||||
serde_json = "1.0.73"
|
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
warp = "0.3.2"
|
warp = "0.3.2"
|
||||||
|
|
25
README.md
25
README.md
|
@ -1,25 +0,0 @@
|
||||||
# geoip-api
|
|
||||||
|
|
||||||
HTTP API for converting an IPv4 address to a country code.
|
|
||||||
|
|
||||||
Uses the [`iptoasn.com`](https://iptoasn.com) dataset.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Grab the latest `ip2asn-v4.tsv.gz` and extract it to `data/ip2asn-v4.tsv`.
|
|
||||||
|
|
||||||
Then just use `cargo` as usual.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ http GET 'http://127.0.0.1:8000/192.168.0.1'
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"asn": 0,
|
|
||||||
"country_code": "None",
|
|
||||||
"asn_desc": "Not routed"
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
84
src/main.rs
84
src/main.rs
|
@ -1,85 +1,7 @@
|
||||||
use itertools::Itertools;
|
use warp::Filter;
|
||||||
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<RangeRecord> {
|
|
||||||
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<Vec<RangeRecord>>;
|
|
||||||
fn with_ranges(
|
|
||||||
ranges: RangeData,
|
|
||||||
) -> impl Filter<Extract = (RangeData,), Error = std::convert::Infallible> + Clone {
|
|
||||||
warp::any().map(move || ranges.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_range_data(ip: String, ranges: RangeData) -> impl warp::Reply {
|
|
||||||
if let Ok(ipv4) = ip.parse::<Ipv4Addr>() {
|
|
||||||
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
eprintln!("[i] Parsing IP ASN data...");
|
let hello = warp::any().map(|| "Hello!".to_string());
|
||||||
let ranges: RangeData = Arc::new(parse_ranges());
|
warp::serve(hello).bind(([127, 0, 0, 1], 8000)).await;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue