ip2asn

A high-performance, memory-efficient Rust crate for mapping IP addresses to
Autonomous System (AS) information with sub-microsecond lookups.
Features
- High Performance: Sub-microsecond longest-prefix match lookups using a
PATRICIA trie (
ip_network_table
).
- Memory Efficient: Uses string interning and optimized data structures to
minimize memory footprint.
- Ergonomic API: A simple, chainable Builder pattern for easy configuration
and map creation.
- Flexible Data Sources: Load data from any
std::io::Read
source (files,
in-memory buffers, etc.).
- Gzip Support: Transparently decompresses
.gz
data sources out of the
box.
- Remote Fetching: An optional
fetch
feature allows building the map
directly from a URL.
- Async-Friendly Lookups: An owned
AsnInfo
struct is available via
lookup_owned()
for safe, lifetime-free use in async or threaded contexts.
- Serde Support: An optional
serde
feature allows AsnInfo
to be
serialized and deserialized.
- Robust Error Handling: Supports a
strict
mode to fail on any parsing
error and a flexible on_warning
callback for custom logging.
- Runtime Agnostic: A fully synchronous core library that works with any
async runtime (
tokio
, smol
, etc.) or in non-async applications.
Quick Start
Add ip2asn
to your Cargo.toml
:
[dependencies]
ip2asn = "0.1.1"
Then, use the Builder
to load your data and perform lookups.
use ip2asn::{Builder, IpAsnMap};
use std::net::IpAddr;
fn main() -> Result<(), ip2asn::Error> {
let data = "31.13.64.0\t31.13.127.255\t32934\tUS\tFACEBOOK-AS";
let map = Builder::new()
.with_source(data.as_bytes())?
.build()?;
let ip: IpAddr = "31.13.100.100".parse().unwrap();
if let Some(info) = map.lookup(ip) {
assert_eq!(info.network, "31.13.64.0/18".parse().unwrap());
assert_eq!(info.asn, 32934);
assert_eq!(info.country_code, "US");
assert_eq!(info.organization, "FACEBOOK-AS");
println!(
"{} -> AS{} {} ({}) in {}",
ip, info.asn, info.organization, info.country_code, info.network
);
}
Ok(())
}
Creating an Empty Map
For applications that start with an empty map and load data later, you can use
IpAsnMap::new()
or Default::default()
.
use ip2asn::IpAsnMap;
let empty_map = IpAsnMap::new();
assert!(empty_map.lookup("8.8.8.8".parse().unwrap()).is_none());
Async-Friendly Lookups
The standard lookup
method returns a view with a lifetime tied to the map.
For async code or situations where you need to store the result, use
lookup_owned
.
use ip2asn::{Builder, AsnInfo}; use std::net::IpAddr;
# fn main() -> Result<(), ip2asn::Error> {
# let data = "1.0.0.0\t1.0.0.255\t13335\tAU\tCLOUDFLARENET";
# let map = Builder::new().with_source(data.as_bytes())?.build()?;
let ip: IpAddr = "1.0.0.1".parse().unwrap();
if let Some(info) = map.lookup_owned(ip) {
assert_eq!(info.asn, 13335);
}
# Ok(())
# }
Fetching from a URL
With the fetch
feature enabled, you can build the map directly from a remote
data source.
[dependencies]
ip2asn = { version = "0.1.1", features = ["fetch"] }
use ip2asn::{Builder, IpAsnMap};
use std::net::IpAddr;
fn main() -> Result<(), ip2asn::Error> {
let url = "https://iptoasn.com/data/ip2asn-combined.tsv.gz";
let map = Builder::new().from_url(url)?.build()?;
let ip: IpAddr = "1.0.0.1".parse().unwrap();
if let Some(info) = map.lookup(ip) {
assert_eq!(info.asn, 13335); }
Ok(())
}
Serialization with Serde
Enable the serde
feature to serialize and deserialize the AsnInfo
struct.
[dependencies]
ip2asn = { version = "0.1.1", features = ["serde"] }
serde_json = "1.0"
# #[cfg(feature = "serde")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# use ip2asn::{Builder, AsnInfo};
# let data = "1.0.0.0\t1.0.0.255\t13335\tAU\tCLOUDFLARENET";
# let map = Builder::new().with_source(data.as_bytes())?.build()?;
# let info = map.lookup_owned("1.0.0.1".parse()?).unwrap();
let serialized = serde_json::to_string(&info)?;
println!("{}", serialized);
let deserialized: AsnInfo = serde_json::from_str(&serialized)?;
assert_eq!(info, deserialized);
# Ok(())
# }
# #[cfg(not(feature = "serde"))]
# fn main() {}
Error Handling
By default, the builder skips lines that cannot be parsed. You can enable
strict
mode to fail fast or use the on_warning
callback for custom logging.
use ip2asn::{Builder, Warning};
use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
fn main() -> Result<(), ip2asn::Error> {
let malformed_data = "1.0.0.0\t1.0.0.255\t13335\tAU\tCLOUDFLARENET\nthis is not a valid line";
let result = Builder::new().with_source(malformed_data.as_bytes())?.strict().build();
assert!(result.is_err());
let warning_count = Arc::new(AtomicUsize::new(0));
let count_clone = warning_count.clone();
let map = Builder::new()
.with_source(malformed_data.as_bytes())?
.on_warning(Box::new(move |warning: Warning| {
eprintln!("Builder warning: {warning:?}");
count_clone.fetch_add(1, Ordering::SeqCst);
}))
.build()?;
assert_eq!(warning_count.load(Ordering::SeqCst), 1);
Ok(())
}
License
This project is licensed under the MIT License.