# How Sniffnet Uses MMDB Readers for GeoIP and ASN Data Lookup

> Discover how Sniffnet leverages MMDB readers for efficient GeoIP and ASN data lookup, providing location and network insights for every IP address. Learn more!

- Repository: [Giuliano Bellini/sniffnet](https://github.com/GyulyVGC/sniffnet)
- Tags: deep-dive
- Published: 2026-04-28

---

**Sniffnet wraps MaxMind GeoIP2 databases in a thread-safe `MmdbReader` abstraction to resolve geographic locations and Autonomous System Numbers for every IP address, falling back to embedded GeoLite2 databases when custom files aren't provided.**

Sniffnet uses MMDB readers for geoIP and ASN data to enrich network traffic analysis with geographic and organizational context. According to the GyulyVGC/sniffnet source code, the implementation centers on a Rust wrapping layer that isolates the raw MaxMind API behind the `MmdbReader` type, providing safe concurrent access across the application's packet processing threads.

## The MmdbReader Abstraction

The core interface is defined in [`src/mmdb/types/mmdb_reader.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/types/mmdb_reader.rs), where the `MmdbReader` enum abstracts over different database sources. This design allows Sniffnet to handle both built-in and user-supplied MaxMind databases uniformly while maintaining a consistent API for the rest of the application.

### Reader Variants

The `MmdbReader` enum defines three variants to handle all database states:

- **`Default(Reader<&'static [u8]>)`** – Wraps the embedded GeoLite2 database compiled into the binary using `include_bytes!`
- **`Custom(Reader<Vec<u8>>)`** – Wraps a user-supplied `.mmdb` file loaded from the filesystem at runtime
- **`Empty`** – A fallback variant used when database loading fails entirely, ensuring the application continues functioning

### Database Loading Logic

The `from(path, default_bytes)` constructor implements a hierarchical loading strategy. It first attempts to open a custom file using `Reader::open_readfile`. If this operation fails or the path is empty, it automatically falls back to `Reader::from_source` using the compiled-in bytes. This ensures Sniffnet functions immediately after installation without requiring manual database downloads.

### Generic Lookup Interface

The `lookup<T>(&self, ip)` method forwards queries to the underlying MaxMind reader and decodes the result into the requested type `T`. This generic approach supports both country and ASN record formats without code duplication, returning `Option`-like results that the calling code handles explicitly.

## Thread-Safe Reader Containers

Sniffnet groups the two required readers in the `MmdbReaders` struct, also located in [`src/mmdb/types/mmdb_reader.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/types/mmdb_reader.rs). This container holds both a **country** reader and an **ASN** reader, each wrapped in `Arc` to enable cheap, thread-safe sharing across multiple async tasks and worker threads in the packet processing pipeline.

## Resolving IP Addresses to Countries

Country resolution is implemented in [`src/mmdb/country.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/country.rs). The `get_country(address, country_db_reader)` function calls `country_db_reader.lookup::<MmdbCountryEntry>(address)` to query the database.

If the lookup returns a valid record, the function converts the `MmdbCountryEntry` into Sniffnet's internal `Country` enum using the ISO country code. When no record exists for the IP address, the function returns `Country::ZZ` representing an unknown location, ensuring the UI can always display a valid value.

## Mapping IPs to Autonomous Systems

ASN resolution follows an identical pattern in [`src/mmdb/asn.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/asn.rs). The `get_asn(address, asn_db_reader)` function performs `lookup::<MmdbAsnEntry>` against the ASN database and converts successful results into Sniffnet's internal `Asn` struct, which contains both the AS number and the organization name.

For unknown IP addresses or database misses, the function returns an empty `Asn` instance rather than failing, ensuring the packet processing pipeline remains stable even for private or newly allocated IP ranges.

## Integration in the Packet Processing Pipeline

The actual enrichment happens in [`src/networking/parse_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/parse_packets.rs) within the `reverse_dns_lookups` thread. For each resolved host address, the code performs both lookups using the shared `MmdbReaders` instance:

```rust
let country = get_country(&address_to_lookup, &mmdb_readers.country);
let asn     = get_asn(&address_to_lookup, &mmdb_readers.asn);

let new_host = Host {
    domain: get_domain_from_r_dns(rdns.clone()),
    asn,
    country,
};

```

These values populate the `Host` structure that UI components later display in the main traffic overview, allowing users to see geographic and network ownership data for each connection endpoint without blocking the packet capture thread.

## Initialization and Embedded Databases

At application startup, Sniffnet initializes the readers using `MmdbReader::from` with the embedded constants:

```rust
const COUNTRY_MMDB: &[u8] = include_bytes!("resources/DB/GeoLite2-Country.mmdb");
const ASN_MMDB: &[u8]      = include_bytes!("resources/DB/GeoLite2-ASN.mmdb");

let country_reader = MmdbReader::from(&custom_country_path, COUNTRY_MMDB);
let asn_reader     = MmdbReader::from(&custom_asn_path, ASN_MMDB);

let mmdb_readers = MmdbReaders {
    country: Arc::new(country_reader),
    asn:     Arc::new(asn_reader),
};

```

Users can override these defaults by placing custom `.mmdb` files in the expected configuration directory. Sniffnet automatically detects and loads these as `Custom` readers, while the compiled-in `Default` readers ensure the application never ships without baseline geographic data.

## Summary

- Sniffnet encapsulates MaxMind's GeoIP2 API in the `MmdbReader` abstraction located in [`src/mmdb/types/mmdb_reader.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/types/mmdb_reader.rs)
- The system supports three database states: embedded defaults (`Default`), user-supplied files (`Custom`), and failure fallbacks (`Empty`)
- Country resolution in [`src/mmdb/country.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/country.rs) returns `Country::ZZ` for unknown IPs, while ASN resolution in [`src/mmdb/asn.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/mmdb/asn.rs) returns empty structs for missing data
- The `MmdbReaders` container uses `Arc` wrapping to share readers safely across the `reverse_dns_lookups` thread in [`src/networking/parse_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/parse_packets.rs)
- Embedded GeoLite2 databases ensure the application functions immediately after installation without requiring manual database downloads

## Frequently Asked Questions

### What is an MMDB file and why does Sniffnet use it?

MMDB (MaxMind DB) is a binary database format optimized for fast IP address lookups. Sniffnet uses MMDB files because they provide efficient, memory-mapped access to GeoIP2 data, allowing the application to resolve millions of IP addresses to countries and ASNs with minimal performance overhead compared to text-based alternatives.

### How does Sniffnet handle missing or corrupted MMDB database files?

Sniffnet handles database loading failures gracefully through the `Empty` variant of `MmdbReader`. If `Reader::open_readfile` fails to parse a custom database, or if the embedded bytes are somehow corrupted, the system falls back to the `Empty` state. This ensures the packet capture pipeline continues operating, simply displaying unknown country codes and empty ASN information rather than crashing.

### Can I use my own MaxMind GeoIP2 database with Sniffnet instead of the embedded GeoLite2 files?

Yes. Sniffnet supports custom databases via the `Custom` variant of `MmdbReader`. When the `from(path, default_bytes)` constructor detects a valid file at the user-provided path, it loads it as a `Reader<Vec<u8>>`. This allows integration with paid GeoIP2 Precision databases or updated GeoLite2 files simply by placing them in the application's configuration directory, which Sniffnet checks during initialization.

### Is the MMDB lookup in Sniffnet thread-safe for concurrent packet processing?

Yes. The `MmdbReader` implementation is thread-safe, and Sniffnet explicitly wraps each reader in `Arc` within the `MmdbReaders` struct. This design allows multiple threads, including the `reverse_dns_lookups` task in [`src/networking/parse_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/parse_packets.rs), to query the same database instance concurrently without data races or performance degradation.