rdap 0.2.0

A modern RDAP (Registration Data Access Protocol) client
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
# 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:

| 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

```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
```

| 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

```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
rdap -f whois AS213605 | grep "mnt-by" | awk '{print $2}'
```

### 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/