Reference manual

whoisd(8)

Section 8 · System Administration Commands · One Ring 1.0

Name #

whoisd — One Ring multi-RIR WHOIS server.

Synopsis #

whoisd [-c file | --config file]
       [-s | --stats] [-r | --reload]
       [-v | --version] [-h | --help]

Description #

whoisd is a multi-core WHOIS server (RFC 3912) that downloads, merges, and serves the bulk RPSL dumps of all five Regional Internet Registries (RIPE, ARIN, APNIC, LACNIC, AFRINIC) from a single TCP endpoint. One query reaches every registry regardless of which one owns the address space.

The server keeps the merged database in memory, indexed by primary key, NIC handle, domain name, and a 128-bit binary radix trie of IP prefixes (longest-prefix-match returns the most specific allocation). Each upstream is polled on a configurable cadence with conditional If-Modified-Since requests, and the in-memory store is rebuilt only when at least one source actually changed. A UTC time-of-day anchor guarantees at least one fresh attempt around the RIRs' daily publish window.

A live stats subsystem tracks every in-flight connection (source, query, state, duration, bytes transferred) and dumps a JSON snapshot to disk on a configurable cadence (sub-second supported). An optional embedded HTTP listener serves both a single-file dashboard and the JSON for external consumers.

Options #

-c file, --config file
Path to the YAML configuration file. Default: /etc/whoisd.yaml.
-s, --stats
Fetch the live status JSON from the running server's stats HTTP listener (as configured by stats.http_bind) and print a pretty, multi-column summary to standard output. The endpoint URL is discovered from the same configuration file. Exits without starting the server.
-r, --reload
Signal the running whoisd to reload its configuration. Looks up the pid file (see Files) and sends SIGHUP. Exits 0 on a successful signal delivery, 1 if no pid file is found or the signal cannot be delivered. Does not wait for the reload to complete on the daemon side — observe the result via the daemon's log output or whoisd --stats.
-v, --version
Print the binary's version string and exit. The string uses a two-number (MAJOR.MINOR) scheme and can be stamped at build time via go build -ldflags "-X main.Version=1.2".
-h, --help
Show the built-in usage summary and exit.

Configuration file #

The configuration file is YAML. All fields have sensible defaults; an empty file is a valid configuration but you must enable at least one source under dbase.sources. The top-level keys are server, dbase, acl, rate_limit, stats, and log.

server #

bind
TCP listen address for the WHOIS protocol, in host:port form. Default: 0.0.0.0:43. Binding to port 43 requires either root or setcap cap_net_bind_service=+ep ./whoisd.
read_timeout
Maximum time to wait for the client's query line. Default: 5s.
write_timeout
Maximum time spent writing the response. Default: 30s.
max_concurrent
Upper bound on concurrent in-flight queries. Excess clients receive %% server busy, try again. Default: 1024.
max_query_bytes
Caps the query line length to defeat abusive clients. Default: 512.
pid_file
Path the daemon writes its pid to on startup, and that whoisd --reload reads to locate the running instance. The parent directory is created (0755) if missing. Set to the empty string to disable pid-file management entirely (e.g. when running under a supervisor that owns the pid via other means); --reload then has nothing to signal. Default: /run/whoisd.pid.

dbase #

cache_dir
Local directory where downloaded *.gz RIR dumps are cached. One subdirectory per source. Default: ./var/dbase.
poll_interval
How often to ask each upstream "has anything changed?". Polls are cheap because of conditional GETs. Default: 1h. Minimum: 30s.
daily_at
UTC time-of-day (HH:MM) at which to schedule one guaranteed refresh attempt per day. Picks up the RIRs' daily publishes promptly even if poll_interval is long. Set to the empty string ("") to disable the anchor. Default: 02:00.
retry_on_failure
Backoff applied between attempts after a refresh fails. Default: 5m.
sources
List of upstream databases to fetch and merge. Each entry is an object with the following fields:
  • name — symbolic name used as the per-source cache subdirectory and in stats output (e.g. ripe, arin).
  • enabled — whether to fetch this source. Setting any source to false skips it.
  • formatsplit | single. split fetches one .gz per RPSL class from ${url}/${db_name}.db.${class}.gz (RIPE, APNIC). single fetches one large .gz directly from url (ARIN, LACNIC, AFRINIC).
  • url — directory URL for split, or the full file URL for single.
  • db_name — file-name prefix used by the split format. Defaults to name if omitted.
  • classes — list of RPSL classes to fetch (only meaningful for split). Typical full set: inetnum, inet6num, route, route6, aut-num, as-block, as-set, domain, mntner, organisation, person, role, irt, key-cert, filter-set, inet-rtr, peering-set, route-set, rtr-set.

acl #

The ACL is a Binary Encoded Radix Tree (BERT) of CIDR prefixes covering both IPv4 and IPv6 in one structure. The longest matching prefix wins.

default
Policy applied when no explicit allow/deny rule matches: allow | deny. Default: allow.
allow
List of CIDRs (or bare IPs interpreted as /32//128) that are permitted. Crucially, sources matched by an entry in allow are also exempt from the rate limiter (see rate_limit). Sources permitted only via the default policy are still rate-limited.
deny
List of CIDRs that are explicitly rejected with %% access denied by policy.

The loopback prefixes 127.0.0.0/8 and ::1/128 are always implicitly allowed (and rate-limit-exempt) so operators can always reach the server locally.

rate_limit #

Per-source token-bucket limiter. Source addresses are aggregated to a configurable prefix length to prevent scanners from hiding in address space.

enabled
Master switch. Default: false.
rps
Sustained queries per second per aggregated source. Required when enabled is true.
burst
Token-bucket size above rps before throttling kicks in. Required when enabled is true.
v4_prefix
Prefix length to aggregate IPv4 sources by (0–32). Default: 32 (per-IP). Most operators want 24 to lump abusive subnets together.
v6_prefix
Prefix length to aggregate IPv6 sources by (0–128). Default: 128. Typical: 48.
idle_ttl
How long an idle per-source bucket lives in memory before garbage collection. Default: 10m.

stats #

enabled
Master switch for the stats subsystem (counters, live connection registry, JSON dumps, optional HTTP listener). Default: false.
dump_path
Path of the JSON file rewritten atomically every dump_interval. Empty disables the disk dump. The HTTP listener can still serve in-memory snapshots even with no disk dump configured.
dump_interval
Cadence at which the snapshot is regenerated and (optionally) flushed to disk. Sub-second values are allowed ("200ms", "500ms"). Minimum: 50ms. Default: 1s.
http_bind
TCP listen address for the embedded HTTP dashboard. Serves GET / (single-file HTML dashboard) and GET /stats.json (current snapshot). Empty disables the listener.

log #

level
Verbosity. One of debug, info, warn, error, or none (also accepts off or silent). Each level includes everything above it. Default: info.

A small set of lifecycle events (server starting with version, each listener bind, each (re)load completion) is logged at a custom NOTICE level above error, so it is emitted regardless of the configured filter except none.

Signals #

SIGHUP
Re-read the configuration file and apply the reloadable subset: acl.*, rate_limit.*, log.level, stats.dump_interval, and server.bind. Each sub-step is independent — a failure in one is logged and skipped, the others still apply. Changes to fields outside this subset (notably dbase.*, the server deadlines, and server.max_concurrent) require a full restart. The same effect is available from the CLI via whoisd --reload.
SIGINT, SIGTERM
Initiate a graceful shutdown: stops accepting new connections, waits for in-flight queries to finish, flushes the final stats snapshot, and exits.

Files #

/etc/whoisd.yaml
Default configuration file location (override with -c).
cache_dir/source/source.db.class.gz
Cached RIR dumps. Each enabled source has its own subdirectory; the file naming mirrors the upstream layout (or just source.db.gz for single-file formats).
dump_path
The current stats JSON snapshot, atomically rewritten every dump_interval.
server.pid_file
Configurable pid file path. Written on startup and read back by whoisd --reload. Default: /run/whoisd.pid.

Exit status #

0
Clean shutdown after SIGINT/SIGTERM, or a successful one-shot operation (--version, --stats, --reload).
1
Runtime failure: initial database load failed, the listener died, or the server otherwise stopped on an error. Also returned by --stats and --reload when they cannot reach the running instance.
2
Startup failure: configuration could not be loaded, parsed, validated, or the ACL could not be built.

Examples #

Run the server with a custom config:

whoisd --config /etc/whoisd.yaml

Query the running server (default WHOIS client, port from server.bind):

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'

Inspect the live status without disturbing the server:

whoisd --stats

Apply changes from the config file without a restart:

whoisd --reload          # or: kill -HUP $(pidof whoisd)

Bind to the privileged port 43 without running as root:

setcap cap_net_bind_service=+ep /usr/local/bin/whoisd

Notes #

Memory footprint scales with the size of the merged store. With all five RIRs loaded, expect roughly 18–20 GiB resident. Startup time is dominated by the initial download and parse; subsequent starts reuse the on-disk cache and are bound by parse time alone.

During a reload-triggered re-index the resident set briefly spikes to roughly 2× steady-state, because the new in-memory store is built alongside the old one before an atomic swap.

whoisd is a single static binary with no runtime dependencies beyond the host operating system. It does not require a database server, message broker, or external state store.

See also #

whois(1), yaml(5)

RIPE Database split-format documentation: https://ftp.ripe.net/ripe/dbase/split/

Project home: https://github.com/hackman/One_Ring