# 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
```bash
git clone https://github.com/Akaere-NetWorks/rdap.git
cd rdap
cargo build --release
sudo cp target/release/rdap /usr/local/bin/
```
### Using Cargo
```bash
cargo install rdap
```
## Usage
### Basic Queries
```bash
# 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):
```bash
# 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:
| `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
```bash
# 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
```
| `text` | Colored terminal output (default) |
| `whois` | Traditional WHOIS/RPSL-style plain text, pipe-friendly |
| `json` | Compact JSON |
| `json-pretty` | Pretty-printed JSON |
### Advanced Options
```bash
# 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
```bash
$ 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
```bash
$ 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
```bash
$ 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
```bash
$ 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
```bash
$ 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
```bash
$ 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`:
```toml
[dependencies]
rdap = { git = "https://github.com/Akaere-NetWorks/rdap.git" }
tokio = { version = "1.35", features = ["full"] }
```
Or use a specific version/branch:
```toml
[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
```rust
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
```rust
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
```rust
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
```rust
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:
```bash
# CLI equivalent
### Custom Server
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
- [RFC 7480](https://tools.ietf.org/html/rfc7480) - HTTP Usage in RDAP
- [RFC 7482](https://tools.ietf.org/html/rfc7482) - RDAP Query Format
- [RFC 7483](https://tools.ietf.org/html/rfc7483) - JSON Responses for RDAP
- [RFC 7484](https://tools.ietf.org/html/rfc7484) - Finding the Authoritative RDAP Service
- [RFC 8521](https://tools.ietf.org/html/rfc8521) - RDAP Object Tagging (entity handle bootstrap)
- [RFC 6350](https://tools.ietf.org/html/rfc6350) - vCard Format
- [RFC 7095](https://tools.ietf.org/html/rfc7095) - jCard
## License
MIT License - see LICENSE file for details
## Author
AKAERE NETWORKS TECHNOLOGY LTD
## Links
- IANA RDAP Bootstrap: https://data.iana.org/rdap/
- IANA Object Tags Registry: https://data.iana.org/rdap/object-tags.json
- RDAP Working Group: https://datatracker.ietf.org/wg/weirds/