One WHOIS server
to query them all.
A multi-core Go WHOIS server that downloads, merges, and serves the bulk RPSL dumps of every Regional Internet Registry — under one query endpoint.
"One Ring to bind them, one Ring to find them..."
Why One Ring?
Five RIRs, one endpoint
One query reaches every Regional Internet Registry. No more remembering which whois server owns which prefix.
Built for scale
Goroutine-per-connection, bounded by a concurrency semaphore, designed to saturate every core. Lookups are O(prefix-length).
Smart auto-update
Conditional GETs keep polling cheap. A UTC daily anchor catches each RIR's daily publish without busy-polling.
BERT ACL
Binary Encoded Radix Tree handles IPv4 and IPv6 in one structure, longest-prefix wins. Surgical exceptions over broad ranges work.
Per-source rate limits
Token bucket per source, aggregated to /24 (v4) and /48 (v6) so scanners can't hide in address space.
Live dashboard
Modern server-status page polls a sub-second JSON dump. See every in-flight connection — source, query, duration, bytes.
Quickstart
Build
# Go 1.22+
git clone https://github.com/hackman/One_Ring.git
cd One_Ring
go build -o whoisd .
Run
./whoisd -c config.yaml
First start downloads the five RIR dumps into /var/whoisd/
and indexes them. Subsequent starts reuse the cache.
Install
# As root:
tar xf one-ring-VER.tgz
cd one-ring-VER
./install.sh
Query
# anywhere in the world
whois -h 127.0.0.1 -p 4343 8.8.8.8
whois -h 127.0.0.1 -p 4343 AS3333
whois -h 127.0.0.1 -p 4343 -- '-m 193.0.0.0/16'
Open the dashboard
# default http_bind
open http://127.0.0.1:8043/
The live stats dashboard
Apache server-status, but for WHOIS — and prettier. Stats are written to a JSON file at a configurable cadence (sub-second supported) and served by an optional embedded HTTP listener.
live qps total bytes-out denied errors
12 187.4 2,461,902 14.8 GiB 37 2
# src query state dur out
214 198.51.100.7 8.8.8.8 writing 0.04s 3.2 KiB
213 203.0.113.42 AS3333 writing 0.11s 2.1 KiB
212 2001:db8::42 193.0.6.139 looking_up 0.02s 0 B
211 10.0.0.5 ARIN-OPS reading 1.4 s 0 B
How it compares
Without the One Ring
- Hop between five RIR servers depending on the prefix.
- Live with their rate limits and outages.
- No view into your own server's load.
- Cobble together cron + curl + gzip + parser when you mirror.
With the One Ring
- One endpoint, one query, all five registries.
- You set the rate limit. Your ACL. Your latency.
- Live dashboard with per-connection visibility.
- Sub-second JSON stats for any monitoring stack.
Configurable everything
dbase:
cache_dir: "/var/dbase"
poll_interval: "1h" # conditional GETs, cheap
daily_at: "03:00" # UTC anchor after RIR publish
sources:
- { name: ripe, format: split, url: "https://ftp.ripe.net/ripe/dbase/split", ... }
- { name: arin, format: single, url: "https://ftp.arin.net/pub/rr/arin.db.gz" }
- { name: apnic, format: split, url: "https://ftp.apnic.net/apnic/whois", ... }
- { name: lacnic, format: single, url: "https://ftp.lacnic.net/lacnic/dbase/lacnic.db.gz" }
- { name: afrinic, format: single, url: "https://ftp.afrinic.net/pub/dbase/afrinic.db.gz" }
acl:
default: "allow"
allow: ["10.0.0.0/8", "192.168.0.0/16"]
rate_limit:
rps: 5
burst: 20
stats:
dump_interval: "500ms" # sub-second OK
http_bind: "127.0.0.1:8080"
Get the one ring.
MIT-licensed, single static binary, designed for production.