rdapify 0.2.1

Unified, secure, high-performance RDAP client with built-in SSRF protection and privacy controls
Documentation

rdapify

A fast, secure, production-ready RDAP client library for Rust.

RDAP (Registration Data Access Protocol) is the modern replacement for WHOIS, defined in RFC 9083 and RFC 9224.

Crates.io docs.rs CI License: MIT

rdapify ecosystem

Library Language Package
rdapify-rsyou are here Rust rdapify on crates.io
RDAPify TypeScript / Node.js rdapify on npm
rdapify-nd Node.js (Rust native) rdapify-nd on npm
rdapify-py Python (Rust native) rdapify-py on PyPI

Features

  • 5 query types — domain, IP, ASN, nameserver, entity
  • IANA Bootstrap (RFC 9224) — automatic server discovery, no manual configuration needed
  • SSRF protection — blocks requests to private, loopback, and link-local addresses
  • In-memory cache — configurable TTL and capacity, lock-free via DashMap
  • IDN support — accepts Unicode domain names, normalises to Punycode automatically
  • Retry with back-off — exponential back-off on network errors and 5xx/429 responses
  • Zero OpenSSL — uses rustls (pure Rust TLS)
  • Async-first — built on tokio

Installation

[dependencies]
rdapify = "0.2"

Quick Start

use rdapify::RdapClient;

#[tokio::main]
async fn main() -> rdapify::Result<()> {
    let client = RdapClient::new();

    // Query a domain
    let domain = client.domain("example.com").await?;
    println!("Registrar: {:?}", domain.registrar);
    println!("Expires:   {:?}", domain.expiration_date());

    // Query an IP address
    let ip = client.ip("8.8.8.8").await?;
    println!("Network: {:?}", ip.name);
    println!("Country: {:?}", ip.country);

    // Query an ASN
    let asn = client.asn("AS15169").await?;
    println!("ASN name: {:?}", asn.name);

    Ok(())
}

Usage

Domain Query

let res = client.domain("rust-lang.org").await?;

println!("{}", res.ldh_name.as_deref().unwrap_or("-"));
println!("{:?}", res.status);
println!("{:?}", res.expiration_date());

if let Some(r) = &res.registrar {
    println!("Registrar: {}", r.name.as_deref().unwrap_or("-"));
}

IP Address Query

// IPv4
let res = client.ip("1.1.1.1").await?;

// IPv6
let res = client.ip("2606:4700::1111").await?;

println!("CIDR:    {:?}", res.cidr);
println!("Country: {:?}", res.country);

ASN Query

// Both formats accepted
let res = client.asn("15169").await?;
let res = client.asn("AS15169").await?;

println!("Name: {:?}", res.name);

Nameserver Query

let res = client.nameserver("ns1.example.com").await?;
println!("IPs: {:?}", res.ip_addresses);

Entity Query

let res = client.entity("ARIN-CHA-1", "https://rdap.arin.net/registry").await?;
println!("Name:  {:?}", res.name);
println!("Roles: {:?}", res.roles);

Configuration

use rdapify::{RdapClient, ClientConfig, FetcherConfig, SsrfConfig};
use std::time::Duration;

let client = RdapClient::with_config(ClientConfig {
    cache: true,
    fetcher: FetcherConfig {
        timeout: Duration::from_secs(10),
        max_attempts: 3,
        ..Default::default()
    },
    ssrf: SsrfConfig {
        enabled: true,
        ..Default::default()
    },
    ..Default::default()
})?;

CLI

Enable the cli feature to build the rdapify binary:

rdapify = { version = "0.1", features = ["cli"] }

Or install it directly:

cargo install rdapify --features cli
rdapify domain example.com
rdapify ip 8.8.8.8
rdapify asn AS15169
rdapify nameserver ns1.example.com
rdapify entity ARIN-CHA-1 https://rdap.arin.net/registry

# Machine-readable JSON output
rdapify domain example.com --raw

Performance

All figures are measured with cargo bench (Criterion) on a Linux x86-64 machine. The query benchmarks use a local mock HTTP server (mockito) so results reflect pure Rust overhead — no real network latency is included.

Cache

Benchmark Time
Cache hit (DashMap read, fresh TTL) ~124 ns
Cache miss (key absent) ~24 ns
Cache insert (single write) ~780 ns
Cache eviction (insert at max capacity) ~8.8 µs
Bulk insert — 100 entries ~35 µs
Bulk insert — 1 000 entries ~444 µs

Query pipeline (mock HTTP server)

Benchmark Time Notes
domain() — no cache ~183 µs bootstrap lookup + HTTP fetch + normalise
domain() — cache hit ~2.3 µs ~80× faster than uncached
ip() — no cache ~176 µs
asn() — no cache ~176 µs

Cache brings query latency from ~180 µs → ~2 µs — an 80× speedup for repeated queries within the TTL window.

SSRF validation

Benchmark Time
Public domain URL (allowed) ~141 ns
Public IPv4 URL (allowed) ~220 ns
Private IPv4 URL (blocked at RFC-1918) ~295 ns
Non-HTTPS scheme (blocked immediately) ~145 ns
SSRF disabled (boolean bypass) ~3 ns

Run the benchmarks yourself:

cargo bench
# HTML reports → target/criterion/report/index.html

Language Bindings

Node.js — rdapify-nd

A prebuilt native binding for Node.js. No compiler required — binaries ship for Linux x64, macOS x64/arm64, and Windows x64.

npm install rdapify-nd
const { domain, ip, asn, nameserver, entity } = require('rdapify-nd');

// Domain
const d = await domain('example.com');
console.log(d.registrar?.name);    // "Example Registrar, Inc."
console.log(d.ldhName);            // "example.com"
console.log(d.metadata.timestamp); // "2026-03-21T00:00:00Z"

// IP address
const i = await ip('8.8.8.8');
console.log(i.name);    // "GOOGLE"
console.log(i.country); // "US"

// ASN
const a = await asn('AS15169');
console.log(a.name); // "GOOGLE"

// Nameserver
const ns = await nameserver('ns1.google.com');
console.log(ns.ipAddresses.v4); // ["216.239.32.10"]

// Entity (requires explicit server URL — no global bootstrap for entities)
const e = await entity('ARIN-HN-1', 'https://rdap.arin.net/registry');
console.log(e.handle); // "ARIN-HN-1"

Use with the TypeScript rdapify library for automatic native acceleration:

npm install rdapify rdapify-nd
import { RDAPClient } from 'rdapify';

// backend: 'auto' (default) uses rdapify-nd if installed, falls back to TypeScript
const client = new RDAPClient({ backend: 'auto' });

// Or require it — throws at startup if rdapify-nd is not installed
const client2 = new RDAPClient({ backend: 'native' });

const result = await client.domain('example.com');
console.log(result.metadata.source); // RDAP server URL that served the response

Python — rdapify-py

A prebuilt native extension for Python 3.8+. Ships as abi3 wheels for Linux x64, macOS x64/arm64, and Windows x64.

pip install rdapify-py
import rdapify_py as rdap

# Domain
d = rdap.domain("example.com")
print(d["registrar"]["name"])         # "Example Registrar, Inc."
print(d["ldhName"])                   # "example.com"
print(d["meta"]["queried_at"])        # RFC 3339 timestamp

# IP address
i = rdap.ip("8.8.8.8")
print(i["name"])     # "GOOGLE"
print(i["country"])  # "US"

# ASN
a = rdap.asn("AS15169")
print(a["name"])  # "GOOGLE"

# Nameserver
ns = rdap.nameserver("ns1.google.com")
print(ns["ipAddresses"]["v4"])  # ["216.239.32.10"]

# Entity (requires explicit server URL)
e = rdap.entity("ARIN-HN-1", "https://rdap.arin.net/registry")
print(e["handle"])  # "ARIN-HN-1"

All five functions are synchronous and backed by a tokio runtime under the hood.

MSRV

Minimum supported Rust version: 1.75

License

MIT — see LICENSE