whoisd · the only WHOIS server you will ever need

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..."

RIPEEurope / ME
ARINNorth America
APNICAsia / Pacific
LACNICLatin America
AFRINICAfrica

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.

whoisd · server status
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.