rdap 0.2.0

A modern RDAP (Registration Data Access Protocol) client
Documentation

RDAP Rust Client

A modern, elegant RDAP (Registration Data Access Protocol) client written in Rust with beautiful colored output.

Features

  • Modern & Fast - Asynchronous I/O with Tokio, efficient HTTP client, fast JSON parsing
  • Beautiful Output - Colorized terminal output, pretty-printed tables, clear hierarchical display
  • WHOIS-style Output - Traditional WHOIS/RPSL-style plain text output (-f whois)
  • Full RDAP Support - Domain, IP (v4/v6), ASN, entity, nameserver, and search queries
  • Smart Bootstrap - Automatic IANA bootstrap service discovery for domains, IPs, ASNs, and entities
  • Entity Object Tags - RFC 8521 support for automatic entity handle resolution (e.g. ORG-LA1994-RIPE, YANGY12-ARIN)
  • RIR Shortcuts - Built-in -r/--rir flag with predefined RIR RDAP URLs (no need to memorize server URLs)
  • Query Auto-Detection - Automatically detects domains, IPs, ASNs, and tagged entity handles
  • Configurable - Custom timeouts, explicit server override, multiple output formats

Installation

From Source

git clone https://github.com/Akaere-NetWorks/rdap.git
cd rdap
cargo build --release
sudo cp target/release/rdap /usr/local/bin/

Using Cargo

cargo install rdap

Usage

Basic Queries

# Query a domain
rdap example.com

# Query an IP address
rdap 192.0.2.1
rdap 2001:db8::1

# Query an AS number
rdap AS15169
rdap 15169

# Query an entity (auto-detected by known tag suffix)
rdap ORG-LA1994-RIPE
rdap YANGY12-ARIN

Entity Queries

Entity handles with known RIR tag suffixes (e.g. -RIPE, -ARIN, -APNIC) are automatically detected and resolved via the IANA object tags bootstrap registry (RFC 8521):

# Auto-detected as entity, bootstrapped to the correct RIR
rdap ORG-LA1994-RIPE
rdap YANGY12-ARIN

# For handles without a known tag suffix, use --rir to specify the RIR
rdap -r ripe LiuHaoRan-MNT
rdap -r arin GOGL

# Or use explicit type + server
rdap -t entity -s https://rdap.db.ripe.net/ RIPE-NCC-END-MNT

Supported --rir values:

Name RDAP Server
ripe https://rdap.db.ripe.net/
arin https://rdap.arin.net/registry/
apnic https://rdap.apnic.net/
lacnic https://rdap.lacnic.net/rdap/
afrinic https://rdap.afrinic.net/rdap/
frnic https://rdap.nic.fr/
glauca https://whois-web.as207960.net/rdap/
norid https://rdap.norid.no/

When --rir is specified, the query type defaults to entity (can be overridden with -t).

Output Formats

# Colored terminal output (default)
rdap example.com

# WHOIS/RPSL-style plain text output
rdap -f whois AS213605
rdap -f whois ORG-LA1994-RIPE

# JSON output
rdap -f json example.com
rdap -f json-pretty example.com
Format Description
text Colored terminal output (default)
whois Traditional WHOIS/RPSL-style plain text, pipe-friendly
json Compact JSON
json-pretty Pretty-printed JSON

Advanced Options

# Specify query type explicitly
rdap -t domain example.com
rdap -t entity -r ripe RIPE-NCC-END-MNT

# Use a specific RDAP server
rdap -s https://rdap.verisign.com/com/v1 example.com

# Use a RIR shortcut (mutually exclusive with --server)
rdap -r ripe LiuHaoRan-MNT

# Verbose output
rdap -v example.com

# Set custom timeout (in seconds)
rdap --timeout 60 example.com

Examples

Domain Query

$ rdap example.com

Domain Name: EXAMPLE.COM
Handle: 2336799_DOMAIN_COM-VRSN
Object Class: domain
Status: client delete prohibited
Status: client transfer prohibited
Status: client update prohibited
Nameserver: A.IANA-SERVERS.NET
Nameserver: B.IANA-SERVERS.NET
Delegation Signed: yes
DS Key Tag: 370
DS Algorithm: 13
DS Digest Type: 2
DS Digest: BE74359954660069D5C63D200C39F5603827D7DD02B56F120EE9F3A86764247C
Registration: 1995-08-14T04:00:00Z
Expiration: 2026-08-13T04:00:00Z
Last Changed: 2025-08-14T07:01:39Z
Last Update: 2025-11-04T20:54:25Z

Entity Handle: 376
Role: registrar
Name: RESERVED-Internet Assigned Numbers Authority
IANA Registrar ID: 376

IP Query

$ rdap 8.8.8.8

Handle: NET-8-8-8-0-2
Start Address: 8.8.8.0
End Address: 8.8.8.255
IP Version: v4
Name: GOGL
Type: DIRECT ALLOCATION
Parent Handle: NET-8-0-0-0-0
Status: active
Port43: whois.arin.net
last changed: 2023-12-28T17:24:56-05:00
registration: 2023-12-28T17:24:33-05:00

Entity Handle: GOGL
Role: registrant
Name: Google LLC

AS Number Query

$ rdap AS213605

AS Number: AS213605
Name: Pysio-Research-NetWork
Handle: AS213605
Object Class: autnum
Status: active
Port43: whois.ripe.net
Registration: 2025-01-10T12:53:39Z
Last Changed: 2025-10-14T13:21:47Z

Entity Handle: LA9082-RIPE
Role: administrative
Role: technical
Role: abuse
Name: LiuHaoRan
Email: team@pysio.online
Phone: +86 19934273163

Entity Query

$ rdap ORG-LA1994-RIPE

Entity Handle: ORG-LA1994-RIPE
Name: Liu HaoRan
Email: team@pysio.online
Phone: +86 19934273163
Address: Fuqing East Street, Chenghua District, Chengdu, Sichuan
Port43: whois.ripe.net
registration: 2025-01-07T17:22:50Z
last changed: 2026-01-13T11:23:15Z

WHOIS-style Output

$ rdap -f whois AS213605

aut-num:        AS213605
as-name:        Pysio-Research-NetWork
org:            ORG-LA1994-RIPE
admin-c:        LA9082-RIPE
tech-c:         LA9082-RIPE
abuse-c:        LA9082-RIPE
mnt-by:         AKAERE-NETWORKS-MNT
mnt-by:         LiuHaoRan-MNT
mnt-by:         RIPE-NCC-END-MNT
status:         ACTIVE
created:        2025-01-10T12:53:39Z
last-modified:  2026-02-05T03:28:52Z
source:         RIPE

organisation:   ORG-LA1994-RIPE
org-name:       Liu HaoRan
address:        Fuqing East Street, Chenghua District, Chengdu, Sichuan
phone:          +86 19934273163
source:         RIPE

Entity Query with RIR Shortcut

$ rdap -r ripe -f whois LiuHaoRan-MNT

mntner:         LiuHaoRan-MNT
descr:          LiuHaoRan-MNT
mnt-by:         LiuHaoRan-MNT
created:        2025-01-06T08:29:19Z
last-modified:  2026-01-29T12:28:32Z
source:         RIPE

Library Usage

Add this to your Cargo.toml:

[dependencies]
rdap = { git = "https://github.com/Akaere-NetWorks/rdap.git" }
tokio = { version = "1.35", features = ["full"] }

Or use a specific version/branch:

[dependencies]
# Use main branch
rdap = { git = "https://github.com/Akaere-NetWorks/rdap.git", branch = "main" }

# Or use a specific tag (when available)
# rdap = { git = "https://github.com/Akaere-NetWorks/rdap.git", tag = "v0.1.0" }

# Or use a specific commit
# rdap = { git = "https://github.com/Akaere-NetWorks/rdap.git", rev = "abc123" }

tokio = { version = "1.35", features = ["full"] }

Basic Query

use rdap::{RdapClient, RdapRequest, QueryType};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a client
    let client = RdapClient::new()?;
    
    // Query a domain
    let request = RdapRequest::new(QueryType::Domain, "example.com");
    let result = client.query(&request).await?;
    
    // Display with colored output
    use rdap::display::RdapDisplay;
    result.display(false); // false = non-verbose
    
    Ok(())
}

Auto-Detection

use rdap::{RdapClient, RdapRequest};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    
    // Auto-detect query type (works for domains, IPs, ASNs, and tagged entity handles)
    let query = "ORG-LA1994-RIPE";
    let query_type = RdapRequest::detect_type(query)?;
    
    let request = RdapRequest::new(query_type, query);
    let result = client.query(&request).await?;
    
    // Process the result based on type
    match result {
        rdap::RdapObject::Domain(domain) => {
            println!("Domain: {}", domain.ldh_name.unwrap_or_default());
        }
        rdap::RdapObject::IpNetwork(ip) => {
            println!("IP Network: {}", ip.name.unwrap_or_default());
        }
        rdap::RdapObject::Autnum(asn) => {
            println!("AS Number: AS{}", asn.start_autnum.unwrap_or(0));
        }
        rdap::RdapObject::Entity(entity) => {
            println!("Entity: {}", entity.handle.unwrap_or_default());
        }
        _ => {}
    }
    
    Ok(())
}

RIR Server Lookup

use rdap::{RdapClient, RdapRequest, QueryType};
use rdap::bootstrap::rir_to_rdap_url;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    
    // Resolve RIR name to RDAP URL
    let server = rir_to_rdap_url("ripe").expect("Unknown RIR");
    let request = RdapRequest::new(QueryType::Entity, "LiuHaoRan-MNT")
        .with_server(server);
    
    let result = client.query(&request).await?;
    
    // WHOIS-style output
    let whois_text = rdap::whois::format_whois(&result);
    print!("{}", whois_text);
    
    Ok(())
}

WHOIS Format Output

use rdap::{RdapClient, RdapRequest, QueryType};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    let request = RdapRequest::new(QueryType::Autnum, "AS213605");
    let result = client.query(&request).await?;

    // Convert to WHOIS/RPSL-style plain text
    let whois_text = rdap::whois::format_whois(&result);

    // Print to stdout — pipe-friendly, no colors or escape sequences
    // e.g. rdap_whois | grep "admin-c" | awk '{print $2}'
    print!("{}", whois_text);

    Ok(())
}

The format_whois() output is plain text with no ANSI escape codes, making it suitable for piping to other tools like grep, awk, cut, or custom parsers:

# CLI equivalent
rdap -f whois AS213605 | grep "mnt-by" | awk '{print $2}'

Custom Server

use rdap::{RdapClient, RdapRequest, QueryType};
use url::Url;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    
    // Use a specific RDAP server
    let server = Url::parse("https://rdap.verisign.com/com/v1")?;
    let request = RdapRequest::new(QueryType::Domain, "example.com")
        .with_server(server);
    
    let result = client.query(&request).await?;
    
    Ok(())
}

JSON Output

use rdap::{RdapClient, RdapRequest, QueryType};
use serde_json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    let request = RdapRequest::new(QueryType::Domain, "example.com");
    let result = client.query(&request).await?;
    
    // Serialize to JSON
    let json = serde_json::to_string_pretty(&result)?;
    println!("{}", json);
    
    Ok(())
}

Working with Domain Data

use rdap::{RdapClient, RdapRequest, QueryType, RdapObject};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    let request = RdapRequest::new(QueryType::Domain, "example.com");
    let result = client.query(&request).await?;
    
    if let RdapObject::Domain(domain) = result {
        // Access domain properties
        println!("Domain: {}", domain.ldh_name.unwrap_or_default());
        
        // Check status
        for status in &domain.status {
            println!("Status: {}", status);
        }
        
        // List nameservers
        for ns in &domain.nameservers {
            if let Some(name) = &ns.ldh_name {
                println!("Nameserver: {}", name);
            }
        }
        
        // Check DNSSEC
        if let Some(dnssec) = &domain.secure_dns {
            if let Some(signed) = dnssec.delegation_signed {
                println!("DNSSEC: {}", if signed { "Signed" } else { "Not signed" });
            }
        }
        
        // Access entities (registrar, registrant, etc.)
        for entity in &domain.entities {
            if let Some(handle) = &entity.handle {
                println!("Entity: {} (roles: {:?})", handle, entity.roles);
            }
            
            // Access vCard data
            if let Some(vcard) = &entity.vcard {
                if let Some(name) = vcard.name() {
                    println!("  Name: {}", name);
                }
                if let Some(email) = vcard.email() {
                    println!("  Email: {}", email);
                }
            }
        }
        
        // Access events
        for event in &domain.events {
            println!("Event: {} at {}", event.action, event.date);
        }
    }
    
    Ok(())
}

Working with IP Network Data

use rdap::{RdapClient, RdapRequest, QueryType, RdapObject};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    let request = RdapRequest::new(QueryType::Ip, "8.8.8.8");
    let result = client.query(&request).await?;
    
    if let RdapObject::IpNetwork(network) = result {
        println!("Network: {}", network.name.unwrap_or_default());
        println!("Range: {} - {}", 
            network.start_address.unwrap_or_default(),
            network.end_address.unwrap_or_default()
        );
        println!("Type: {}", network.network_type.unwrap_or_default());
        
        if let Some(country) = &network.country {
            println!("Country: {}", country);
        }
    }
    
    Ok(())
}

Working with AS Number Data

use rdap::{RdapClient, RdapRequest, QueryType, RdapObject};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = RdapClient::new()?;
    let request = RdapRequest::new(QueryType::Autnum, "AS15169");
    let result = client.query(&request).await?;
    
    if let RdapObject::Autnum(asn) = result {
        if let Some(start) = asn.start_autnum {
            println!("AS Number: AS{}", start);
        }
        println!("Name: {}", asn.name.unwrap_or_default());
        println!("Type: {}", asn.as_type.unwrap_or_default());
        
        if let Some(country) = &asn.country {
            println!("Country: {}", country);
        }
    }
    
    Ok(())
}

Error Handling

use rdap::{RdapClient, RdapRequest, QueryType, RdapObject, RdapError};

#[tokio::main]
async fn main() {
    let client = RdapClient::new().unwrap();
    let request = RdapRequest::new(QueryType::Domain, "example.com");
    
    match client.query(&request).await {
        Ok(result) => {
            // Handle successful response
            match result {
                RdapObject::Error(err) => {
                    eprintln!("RDAP Error: {}", err.title.unwrap_or_default());
                    for desc in &err.description {
                        eprintln!("  {}", desc);
                    }
                }
                _ => {
                    use rdap::display::RdapDisplay;
                    result.display(false);
                }
            }
        }
        Err(e) => {
            match e {
                RdapError::Bootstrap(msg) => {
                    eprintln!("Bootstrap error: {}", msg);
                }
                RdapError::Http(err) => {
                    eprintln!("HTTP error: {}", err);
                }
                RdapError::InvalidQuery(msg) => {
                    eprintln!("Invalid query: {}", msg);
                }
                _ => {
                    eprintln!("Error: {}", e);
                }
            }
        }
    }
}

Advanced: Custom Timeout and Configuration

use rdap::{RdapClient, RdapRequest, QueryType};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create client with custom timeout
    let client = RdapClient::new()?
        .with_timeout(Duration::from_secs(30));
    
    let request = RdapRequest::new(QueryType::Domain, "example.com");
    let result = client.query(&request).await?;
    
    Ok(())
}

Architecture

src/
├── lib.rs           # Library entry point
├── main.rs          # CLI entry point
├── error.rs         # Error types
├── models/          # RDAP data models
│   ├── domain.rs
│   ├── entity.rs
│   ├── autnum.rs
│   ├── ip_network.rs
│   ├── nameserver.rs
│   └── ...
├── client.rs        # RDAP client
├── request.rs       # Request builder & query type auto-detection
├── bootstrap.rs     # IANA bootstrap service discovery (incl. RFC 8521 object tags)
├── cache.rs         # Bootstrap cache
├── display.rs       # Pretty colored output formatting
└── whois.rs         # WHOIS/RPSL-style plain text output

RFCs Implemented

License

MIT License - see LICENSE file for details

Author

AKAERE NETWORKS TECHNOLOGY LTD

Links