Using IPinfo's MMDB database with Rust

If you want to use our IPinfo MMDB database in a Rust environment, here are some high-level notes for you:

  • You need to use the Rust MMDB reader library.
  • The Rust MMDB reader crate does not support IPinfo MMDB IP databases out of the box. It is just an MMDB reader library. You have to deserialize/serialize the response from the library.
  • You have to declare a struct based on the IPinfo IP database schema. You can find the database schema in our documentation.
  • The IPinfo Rust Official Library only supports the IPinfo API and does not work with the MMDB database.

Okay, now that we have the fundamentals covered, let’s go through a basic tutorial. You will need Rust, Cargo and all that stuff. For the IPinfo IP database, we will use the IPinfo free IP database, the IP to Country ASN database.

Initializing the Rust project

Initialize the Rust project with the following command with package name being ipinfo_mmdb:

cargo new ipinfo_mmdb
cd ipinfo_mmdb

In your Cargo.toml file, bring in the required crates:

[dependencies]
maxminddb = "0.24.0"
serde = "1.0.197"

Your crate versions can be different. Check out the latest version from their crates.io pages:

After that, you are ready to write some basic code.

Downloading the IPinfo MMDB database

You can use whatever IPinfo IP database you have access to; just make sure you download the MMDB database. We are going to use the IPinfo IP to Country ASN database.

Run the following code from your terminal to download the IP to Country ASN database:

curl -L https://ipinfo.io/data/free/country_asn.mmdb?token=$TOKEN -o country_asn.mmdb

Make sure to replace the $token placeholder with your IPinfo access token. The above command will download the IPinfo IP to Country ASN MMDB database in your directory with the filename country_asn.mmdb

IP Query code in Rust

The [main.rs](http://main.rs) code

use maxminddb::MaxMindDBError;
use std::net::IpAddr;
use serde::{Deserialize, Serialize};

// Declare the struct based on the IPinfo IP database schema
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct IpinfoCountryASN<'a> {
    pub country: Option<&'a str>,
    pub country_name: Option<&'a str>,
    pub continent: Option<&'a str>,
    pub continent_name: Option<&'a str>,
    pub asn: Option<&'a str>,
    pub as_name: Option<&'a str>,
    pub as_domain: Option<&'a str>,
}

fn main() -> Result<(), MaxMindDBError> {
    // Replace with the actual path to your IPinfo IP database MMDB path
    let mmdb_path = "./country_asn.mmdb";

    // Open the MMDB IP database
    let reader = maxminddb::Reader::open_readfile(&mmdb_path)?;

    // Replace with the IP address you want to look up
    let ip_address: IpAddr = "78.35.248.93".parse().expect("Failed to parse IP");

    // Perform the IP address lookup
    let record: IpinfoCountryASN = reader.lookup(ip_address).unwrap();

    // Return IP query response
    println!("Country: {}", record.country.unwrap());
    println!("Country Name: {}", record.country_name.unwrap());
    println!("Continent: {}", record.continent.unwrap());
    println!("Continent Name: {}", record.continent_name.unwrap());
    println!("ASN: {}", record.asn.unwrap());
    println!("AS Organization Name: {}", record.as_name.unwrap());
    println!("AS Domain/Website: {}", record.as_domain.unwrap());
    
    println!("Payload: {:?}", record);

    Ok(())
}

Let’s go through the code block by block.

use statements

use maxminddb::MaxMindDBError;
use std::net::IpAddr;
use serde::{Deserialize, Serialize};
  • maxminddb is the MMDB reader crate. We will also use the MaxMindDBError function to handle IP addresses that are not present in the MMDB database.
  • std::net::IpAddr is from the standard library. We will use it to parse IP addresses from string format to IpAddr format.
  • serde will be used to deserialize and serialize the response from the MMDB IP query response.

Declaring the struct

#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct IpinfoCountryASN<'a> {
    pub country: Option<&'a str>,
    pub country_name: Option<&'a str>,
    pub continent: Option<&'a str>,
    pub continent_name: Option<&'a str>,
    pub asn: Option<&'a str>,
    pub as_name: Option<&'a str>,
    pub as_domain: Option<&'a str>,
}

We will declare a struct using serde. This is based on the IPinfo IP to Country ASN database schema. In the struct declaration, we will include all the IP metadata columns, which do not contain an IP address, as the MMDB reader only returns the IP metadata as a response.

We are using Option<&'a str> for declaring the values for optional string references. Usually, for all our database values, we return a string, except for our privacy detection database, where we return "" (empty string) or true for privacy detection flags (VPN, hosting, proxy, tor, and relay).

Main function

fn main() -> Result<(), MaxMindDBError>

Our main function runs the core lookup logic with errors being caught by MaxMindDBError error type. If the IP address does not exist in the MMDB database or is a bogon IP address, you will see the following error:

thread 'main' panicked at src/main.rs:29:62:
called `Result::unwrap()` on an `Err` value: AddressNotFoundError("Address not found in database")
let mmdb_path = "./country_asn.mmdb";
let reader = maxminddb::Reader::open_readfile(&mmdb_path)?;

Provide the path to the mmdb IP database. Ensure you have the IP database name and path correct, or you will get an IoError. Using the provided path, the MMDB reader crate will convert the MMDB database and create the reader object that will be used to query IP addresses from the MMDB IP database.

let reader = maxminddb::Reader::open_readfile(&mmdb_path)?;
let ip_address: IpAddr = "78.35.248.93".parse().expect("Failed to parse IP address");

This section accepts the input IP address as a string and converts it to the IpAddr data type using the std::net standard library. In this example, we are using the random IP address 78.35.248.93

Then, using the reader object, we look up the input IP address from the MMDB database and store the MMDB response IP metadata in the record variable, which is the type IpinfoCountryASN

    println!("Country: {}", record.country.unwrap());
    println!("Country Name: {}", record.country_name.unwrap());
    println!("Continent: {}", record.continent.unwrap());
    println!("Continent Name: {}", record.continent_name.unwrap());
    println!("ASN: {}", record.asn.unwrap());
    println!("AS Organization Name: {}", record.as_name.unwrap());
    println!("AS Domain/Website: {}", record.as_domain.unwrap());

    println!("Payload: {:?}", record);

Then, we print out the individual values from the record response and the entire payload.

Country: DE
Country Name: Germany
Continent: EU
Continent Name: Europe
ASN: AS8422
AS Organization Name: NetCologne Gesellschaft fur Telekommunikation mbH
AS Domain/Website: netcologne.de

Payload: IpinfoCountryASN { country: Some("DE"), country_name: Some("Germany"), continent: Some("EU"), continent_name: Some("Europe"), asn: Some("AS8422"), as_name: Some("NetCologne Gesellschaft fur Telekommunikation mbH"), as_domain: Some("netcologne.de") }

That is the gist of using our MMDB dataset in Rust.

1 Like

Thanks Abdullah for the amazing guide.
It’ll be helpful for sure.

I’ll come back here in case of further doubts with the implementation.

1 Like

Thank you very much. Really appreciate it!