How Sniffnet Uses MMDB Readers for GeoIP and ASN Data Lookup

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, 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. 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. 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. 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 within the reverse_dns_lookups thread. For each resolved host address, the code performs both lookups using the shared MmdbReaders instance:

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:

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
  • The system supports three database states: embedded defaults (Default), user-supplied files (Custom), and failure fallbacks (Empty)
  • Country resolution in src/mmdb/country.rs returns Country::ZZ for unknown IPs, while ASN resolution in 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
  • 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, to query the same database instance concurrently without data races or performance degradation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →