libmaxminddb-rs
A pure Rust reader and writer for MaxMind DB (MMDB) v2 files. Read IPv4 and IPv6 data with borrowed values, or build deterministic databases with longest-prefix matching.
Install
[dependencies]
libmaxminddb-rs = "0.1"
Default features include the reader, writer, derive macros, SIMD ASCII scanning, and the prepared search tree. For a smaller reader-only build, use default-features = false with features = ["reader", "derive"].
GeoLite2 examples
The repository includes runnable examples for the GeoLite2 Country, City, and ASN databases. Each example downloads its database on first run, stores it under examples/target/database, and uses lookup_borrowed to borrow string data from the reader. The snippets below show the record structs and lookup for each database; they expect the corresponding .mmdb file in the current directory.
Run the complete examples from a checkout with cargo run --example geolite2_country, cargo run --example geolite2_city, or cargo run --example geolite2_asn. More examples are available in the project's examples directory.
GeoLite2 Country
use libmaxminddb_rs::{MmdbDecode, Reader};
use std::net::IpAddr;
#[derive(Debug, MmdbDecode)]
struct GeoLite2Country<'a> {
continent: Option<Continent<'a>>,
country: Option<Country<'a>>,
location: Option<Location<'a>>,
registered_country: Option<Country<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Continent<'a> {
code: Option<&'a str>,
geoname_id: Option<u64>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Country<'a> {
geoname_id: Option<u64>,
iso_code: Option<&'a str>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Names<'a> {
en: Option<&'a str>,
}
#[derive(Debug, MmdbDecode)]
struct Location<'a> {
accuracy_radius: Option<u64>,
latitude: Option<f64>,
longitude: Option<f64>,
time_zone: Option<&'a str>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = Reader::open("GeoLite2-Country.mmdb")?;
let ip: IpAddr = "8.8.8.8".parse()?;
let record: GeoLite2Country<'_> = reader.lookup_borrowed(ip)?;
println!("{record:#?}");
Ok(())
}
GeoLite2 City
use libmaxminddb_rs::{MmdbDecode, Reader};
use std::net::IpAddr;
#[derive(Debug, MmdbDecode)]
struct GeoLite2City<'a> {
continent: Option<Continent<'a>>,
country: Option<Country<'a>>,
city: Option<City<'a>>,
location: Option<Location<'a>>,
postal: Option<Postal<'a>>,
registered_country: Option<Country<'a>>,
subdivisions: Option<Vec<Subdivision<'a>>>,
traits: Option<Traits>,
}
#[derive(Debug, MmdbDecode)]
struct Continent<'a> {
code: Option<&'a str>,
geoname_id: Option<u64>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Country<'a> {
geoname_id: Option<u64>,
iso_code: Option<&'a str>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct City<'a> {
geoname_id: Option<u64>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Names<'a> {
en: Option<&'a str>,
}
#[derive(Debug, MmdbDecode)]
struct Location<'a> {
accuracy_radius: Option<u64>,
latitude: Option<f64>,
longitude: Option<f64>,
time_zone: Option<&'a str>,
}
#[derive(Debug, MmdbDecode)]
struct Postal<'a> {
code: Option<&'a str>,
}
#[derive(Debug, MmdbDecode)]
struct Subdivision<'a> {
geoname_id: Option<u64>,
iso_code: Option<&'a str>,
names: Option<Names<'a>>,
}
#[derive(Debug, MmdbDecode)]
struct Traits {
is_anonymous_proxy: Option<bool>,
is_satellite_provider: Option<bool>,
is_anycast: Option<bool>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = Reader::open("GeoLite2-City.mmdb")?;
let ip: IpAddr = "8.8.8.8".parse()?;
let record: GeoLite2City<'_> = reader.lookup_borrowed(ip)?;
println!("{record:#?}");
Ok(())
}
GeoLite2 ASN
use libmaxminddb_rs::{MmdbDecode, Reader};
use std::net::IpAddr;
#[derive(Debug, MmdbDecode)]
struct GeoLite2Asn<'a> {
autonomous_system_number: u64,
autonomous_system_organization: &'a str,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = Reader::open("GeoLite2-ASN.mmdb")?;
let ip: IpAddr = "8.8.8.8".parse()?;
let record: GeoLite2Asn<'_> = reader.lookup_borrowed(ip)?;
println!("{record:#?}");
Ok(())
}
Build and query a database
This self-contained example writes IPv4 and IPv6 networks, then decodes a typed record whose strings borrow directly from the MMDB buffer:
use libmaxminddb_rs::{MetadataBuilder, MmdbDecode, MmdbEncode, Reader, Writer};
#[derive(Debug, MmdbEncode, MmdbDecode)]
struct Network<'a> {
country: &'a str,
asn: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let metadata = MetadataBuilder::new().ip_version(6).build()?;
let mut writer = Writer::with_metadata(metadata);
writer.insert_encoded(
"198.51.100.0/24".parse()?,
&Network { country: "US", asn: 64512 },
)?;
writer.insert_encoded(
"2001:db8:1::/48".parse()?,
&Network { country: "FR", asn: 64513 },
)?;
let bytes = writer.finish()?;
let reader = Reader::from_bytes(&bytes)?;
let network: Network<'_> = reader.lookup_borrowed("2001:db8:1::42".parse()?)?;
assert_eq!((network.country, network.asn), ("FR", 64513));
let asn = reader.lookup_borrowed_map(
"198.51.100.7".parse()?,
|record: Network<'_>| record.asn,
)?;
assert_eq!(asn, Some(64512));
Ok(())
}
Write a struct to an MMDB file
insert_encoded serializes a struct, and write_to_file finalizes the database on disk. The same struct can be decoded after reopening the file:
use libmaxminddb_rs::{MetadataBuilder, MmdbDecode, MmdbEncode, Reader, Writer};
#[derive(Debug, MmdbEncode, MmdbDecode)]
struct Location<'a> {
country: &'a str,
city: &'a str,
population: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "target/locations.mmdb";
std::fs::create_dir_all("target")?;
let metadata = MetadataBuilder::new().ip_version(4).build()?;
let mut writer = Writer::with_metadata(metadata);
writer.insert_encoded(
"203.0.113.0/24".parse()?,
&Location {
country: "FR",
city: "Paris",
population: 2_100_000,
},
)?;
writer.write_to_file(path)?;
let reader = Reader::open(path)?;
let location: Location<'_> = reader.lookup_borrowed("203.0.113.42".parse()?)?;
assert_eq!(
(location.country, location.city, location.population),
("FR", "Paris", 2_100_000),
);
Ok(())
}
Read an existing MMDB file
lookup_value returns a borrowed generic value. A missing address returns Error::NotFound:
use libmaxminddb_rs::{Error, Reader};
use std::net::IpAddr;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = Reader::open("GeoIP.mmdb")?;
let ip: IpAddr = "8.8.8.8".parse()?;
match reader.lookup_value(ip) {
Ok(value) => println!("{value:?}"),
Err(Error::NotFound) => println!("No matching network"),
Err(error) => return Err(error.into()),
}
Ok(())
}
More examples
The project's examples directory contains runnable Reader and Writer programs, including quickstart, fast IPv4/IPv6 lookups, and custom database writing. Run one with cargo run --example quickstart.
See the API documentation for all lookup and writer methods. Minimum supported Rust version: 1.90. Licensed under MIT or Apache-2.0, at your option.