Whalenet mesh
A fleet mesh where every node is a routable IPv6 /128 with a name anyone can check from the IANA root - and where the tool tells you, per peer, whether the path it is drawing actually exists.
Whalenet is the fleet view of something Whisper already had: an agent is an address, the address is the identity, and the identity is published in DNS where a stranger can validate it without an account. Whalenet adds the part a fleet needs - who else is in it, whether you can reach them, and what the path between you actually is.
The verbs, flags and argument order mirror tailscale, so what you already know transfers. What differs is said out loud rather than left to be discovered.
What is true today
Read this section before the commands. Every line of it is enforced by the tool's own output, so you will meet these facts anyway; meeting them here is cheaper.
| Fact | What it means |
|---|---|
| East-west is relayed by default | Traffic between two of your nodes is carried by the Whisper box both peers terminate on, unless that pair has a direct path and this host's own tunnel has seen the handshake land. The round trip through the box includes the hairpin: 23.8 to 24.3 ms relayed against 0.13 to 0.28 ms direct, about 130x. We hold the plaintext for that hop, which is categorically weaker than a relay that only ever carries ciphertext, and it is the reason a direct path is worth having. No customer pair has been measured on one yet: a node has to declare its private endpoints at connect before it is eligible, and whale status prints relayed for every peer that has not. |
| A cross-box pair has no path at all | Two nodes that terminate on different boxes cannot reach each other. whale status says so per peer rather than drawing a row that implies reachability. |
| Traversal is deliberately small | No STUN, no DERP, and no third party anywhere in it. The cases covered are the ones that need no traversal at all (two nodes on a shared segment, or a peer whose public endpoint nothing rewrites) plus a punch where both ends handshake at the endpoints a box observed them arriving from. A pair where BOTH ends sit behind a NAT that varies its port per destination still relays, and a punch that fails costs latency and nothing else. |
| The namespace is public | Agent names live in public DNS and the reverse tree is walkable. Anyone can enumerate what you publish. |
| Mobile is not supported | There is no iOS or Android Whalenet client, and no date to promise. |
| Direct paths will move the boundary | Once a pair talks directly, revocation depends on both ends applying it, and a peer entry's lifetime is not bounded yet. That is a cooperative boundary, not an enforced one, and it is worth knowing before you rely on it. |
The verbs
$ whisper whale
whisper whale - the Whalenet command shell.
status this node, your fleet, and the path between them
ip this node's address (Whalenet is IPv6-only today)
ping a real round trip to a peer, and what it proves
netcheck what works between this host and the fleet, measured
whois who holds an address, keylessly, validated from the IANA root
dns the resolver in effect, a query with the graph's view, your records
route subnet routes: what this node carries, and what advertising needs
exit-node the ways out, ranked on the graph rather than on latency alone
ssh SSH to a node, host key proven from the IANA DNSSEC root
migrate move a Tailscale tailnet here, plan first, nothing silently widened
serve put a local port in front of your fleet, pinned by the published TLSA
funnel the same port, answered for the whole internet, deliberately
syspolicy the settings your MDM manages here, and the profile that sets them
acl the document that decides who in your fleet may reach what
Joining a node is still separate work: a half-working join would be worse than
none. Everything else here reads before it writes, `migrate plan` changes nothing
at all on either side, and `acl test` answers from the artifact the wire reads.
(Trimmed at the same place the tool stops describing itself: the usage, the per-verb summaries and the global flags follow, and whisper whale <verb> --help is the better place to read them.)
Every transcript on this page is real output from the binary https://get.whisper.online/dl/linux-arm64/whisper serves, whisper version 0.211.0. Nothing here is a mock-up. Two things in it are rewritten rather than captured, and it is worth knowing which: API keys are redacted, and every tenant label is replaced by a placeholder of the form t00000000000000000000000000000001. The addresses are rewritten to match: their routing half is zeroed the same way the tenant label is, so a block here shows you the shape of a real record while resolving to nothing. That matters more than it looks - an address is the PTR and RDAP lookup key, so publishing a real one alongside a redacted label would hand back the label the redaction removed. To check a real one, run whisper whale whois against an address you hold.
Where am I, and what can I reach
netcheck measures rather than asserts. It is the first thing to run on a host that is behaving oddly, and it needs no key.
$ whisper whale netcheck
NETCHECK
ipv6 yes
ipv4 yes
udp yes
anchor box ns1.whisper.online 25.0 ms
mtu client 1280
mtu server 1340
BOX FAMILY ADDRESS UDP/53 TCP/443 NOTE
ns1.whisper.online ipv6 2a05:f480:1400:ab7::fe45:10c 38.4 ms 25.0 ms -
ns1.whisper.online ipv4 78.141.218.153 38.5 ms 25.1 ms -
ns2.whisper.online ipv6 2001:19f0:5000:15f6::fe45:110 35.4 ms 35.4 ms -
ns2.whisper.online ipv4 45.32.186.184 35.4 ms 30.5 ms -
mtu: client is this host's tunnel interface; server is the wg-agents interface on the box (the fleet's published value, not measured from here). The smaller of the two governs.
A v4-only host can still join: the WireGuard endpoint is resolved over both families, so the tunnel rides IPv4 while the identity inside it stays IPv6.
PATH relayed: east-west traffic is carried by the box both peers terminate on. direct-local means the two nodes share a segment and the packets never touch a box; direct-public means the peer's endpoint is reachable as-is; direct-punch means a path was opened through a NAT by both ends handshaking at the endpoints a box observed them arriving from. A punch that fails costs latency and nothing else - the relay carries the peer throughout, and the line under this table says so in those words - and a pair where BOTH ends sit behind a NAT that varies its port per destination still relays. A pair split across two boxes has no path at all. Nothing here is measured until you run `whisper whale ping <peer>`.
The two MTU numbers are the usual cause of "it connects and then large transfers hang": 1280 is the client netstack default, 1340 is what the wg-agents interface on the box publishes, and the smaller one governs.
The fleet, and the path to each peer
$ whisper whale status
NODE
host build-vm
address 2a04:2a01:9::50
tier -
connection not connected
key set (WHISPER_API_KEY)
PEER ADDRESS STATE PATH RTT NAME IN DNS
agent-a7b3c624cc037b3cd 2a04:2a01:0000:0001:0000:0000:c037:b3cd active relayed - a7b3c624cc037b3cd.t00000000000000000000000000000002.agents.whisper.online
agent-aaa1ad6fb73bb9d5b 2a04:2a01:0000:0002:aa1a:d6fb:73bb:9d5b active relayed - aaa1ad6fb73bb9d5b.t00000000000000000000000000000002.agents.whisper.online
PATH relayed: east-west traffic is carried by the box both peers terminate on. direct-local means the two nodes share a segment and the packets never touch a box; direct-public means the peer's endpoint is reachable as-is; direct-punch means a path was opened through a NAT by both ends handshaking at the endpoints a box observed them arriving from. A punch that fails costs latency and nothing else - the relay carries the peer throughout, and the line under this table says so in those words - and a pair where BOTH ends sit behind a NAT that varies its port per destination still relays. A pair split across two boxes has no path at all. Nothing here is measured until you run `whisper whale ping <peer>`.
agents.whisper.online snapshot age: ns1.whisper.online 49m, ns2.whisper.online 49m. Every node is answering from the same recent snapshot.
this host is pinned to 2a04:2a01:9::50, which is not in the fleet this key can see - the node half and the peer half are describing different accounts
(The peer table is trimmed here; the run it was captured from listed the whole fleet.)
Three things worth noticing in that output.
The PATH column is relayed, not a hopeful direct. A direct form is printed only when this host's own tunnel records a handshake that landed, because the handshake never touches a box and the box's answer therefore cannot settle it.
The snapshot age line is how stale each authoritative node's copy of the zone is, asked of each nameserver directly rather than through a resolver. That distinction is the point: the question is not what some node said, it is whether the nodes agree. Who may log in to a node is decided on that node from a signed record, and a node that has lost contact with the primary keeps serving the copy it holds with nothing bounding how long, so two nodes on different snapshots means whichever one replies decides what you are told. When that happens the line says so in those words. It never invents a number it cannot stand behind either: a serial it cannot honestly read as a time, or a nameserver that did not answer, is reported as unknown with the reason rather than dropped.
And the last line is the tool refusing to paper over a mismatch: this host is pinned to an address the presented key cannot see, so the node half and the peer half are describing different accounts, and saying that is more useful than rendering a tidy table that quietly means nothing.
Proving a path rather than believing one
status draws the topology. ping is the one that measures it.
$ whisper whale ping 2a04:2a01:0000:0003:a0c4:6935:6908:b236
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 39.8 ms path=relayed
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 25.6 ms path=relayed
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 26.1 ms path=relayed
3 sent, 3 answered, path relayed, rtt min/avg/max 25.6/30.5/39.8 ms
the path exists and it is relayed by a Whisper box, so this round trip includes the hairpin out to the box and back.
Who holds an address - with no key at all
whois is the verb to hand somebody outside your organisation. It needs no account, and it does not ask you to trust our answer: the name is validated in the client process, from the IANA DNSSEC root trust anchor compiled into the binary, because our own resolver never sets the AD bit and a tool that read AD would be trusting the wrong thing.
$ whisper whale whois 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
WHOIS
target 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
address 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
name ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online
name proof DNSSEC-validated here, from the IANA root
forward confirm AAAA(ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online) contains 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
rdap handle ae3b051ff3bf7f478
rdap name scout
range 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
country NL
status active
trust anchor: IANA DNSSEC root -> ip6.arpa -> the delegation that holds this /128
validated by this client, in-process (our resolver never sets AD). Pass --json for the verbatim RDAP object.
forward confirm is the half that matters: a PTR alone proves nothing, because whoever holds the reverse tree writes it. Resolving the name it gives back and finding the original address in the AAAA is what makes it FCrDNS.
Naming, and what the resolver did with your query
$ whisper whale dns query whisper.online
DNS QUERY
question whisper.online A
resolver 127.0.0.53:53 via the system resolver, no Whisper profile applied on this host
rcode NOERROR 43.1 ms
answer whisper.online. 300 IN A 78.141.218.153
whisper.online. 300 IN A 45.32.186.184
policy none the resolver answered normally and attached no policy signal
graph NONE coverage known-clean
policy is the resolver's own signal, read off the wire. graph is the assessment the resolver consults, not a second verdict computed here.
policy and graph are deliberately two fields. One is what the resolver did; the other is what the graph knows. Collapsing them into a single verdict would hide the case where they disagree, which is exactly the case worth seeing.
Ways out, and what ranks them
$ whisper whale exit-node list
CANDIDATE ASN DENSITY RTT NOTE
ns2.whisper.online AS219419 unknown 35.3 ms the graph holds no routed space for this ASN, so there is no density to compute - absence of a signal, not a clean bill of health
ns1.whisper.online AS219419 unknown 35.3 ms the graph holds no routed space for this ASN, so there is no density to compute - absence of a signal, not a clean bill of health
These are the Whisper egress points this node can use today. A PEER-advertised exit node is kernel-tier only and needs a direct node-to-node path to EVERY node that would use it, which this build does not have for the general case: many pairs get one, but a pair with a port-varying NAT at both ends still relays and a pair split across two boxes has no path at all. Density is the graph's listed-IP ratio for the ASN, read from asnThreatDensity().densityRatio and explain().breakdown.graphDensityRatio - never from the prose in an explanation.
unknown is a deliberate value, not a missing one. An ASN the graph holds no routed space for has no density to compute, and rendering that as 0.00 would read as a clean bill of health for something nobody has looked at.
Subnet routes
A Whisper agent peer is bound to exactly one /128, and that is a security property rather than a limitation to flag away: it is what stops one agent sourcing another's identity. A subnet router is therefore a separate class of peer, kernel-tier only, and direct-path only - two customers routinely advertise the same 10.0.0.0/8, and one shared box cannot hold both.
$ whisper whale route list
PREFIX SOURCE DIRECTION
- - nothing routed
Nothing is connected, so this node routes nothing into a tunnel. Run `whisper connect` first.
No subnet route is advertised, and none can be in this build: run `whisper whale route advertise <prefix>` to see exactly what is missing.
SSH without trust-on-first-use
whale ssh checks the host key in this process against the SSHFP record published for the node, validated from the IANA root trust anchor compiled into the binary. It is not ssh -o VerifyHostKeyDNS=yes: that reads the AD bit, our resolver never sets AD, and it would print a DNS match and then prompt you anyway.
There is no trust-on-first-use. If no SSHFP is published, or the offered key does not match one, the connection is refused and the message says which. Who may log in is decided on the target, by its authorized-keys command reading a DNSSEC-signed ACL, so a removed principal is denied there and the denial lands in the target's auth log. --explain names both halves without connecting.
Coming from Tailscale
whale migrate plan reads their tailnet and writes a plan file. It changes nothing, on either side. Read the plan before you apply it: it enumerates every write in the order it will happen, and names what mapped exactly, what mapped approximately, what mapped to something strictly broader than their rule, and what cannot map at all. A rule that would widen is refused until you pass --accept-widening, and that consent is written into the plan file so it can be audited later.
The Tailscale credential is read from the environment (TS_API_KEY, or TS_OAUTH_CLIENT_ID + TS_OAUTH_CLIENT_SECRET), never from a flag. It is never written to the plan, never logged and never echoed.
Serving a port to your fleet, or to the internet
serve puts a local port behind this node's own /128, on the TLS leaf the published TLSA record already pins - a caller verifies it from the IANA DNSSEC root, no CA and nothing to install. By default only your fleet is answered; the port is still globally routable, so a stranger who reaches it gets a 403 and nothing else.
$ whisper whale serve 8123 --yes
SERVING
scope fleet - your fleet only
url https://a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online/
origin http://127.0.0.1:8123/
address 2a04:2a01:0000:0001:93d1:1cce:c4de:521a
name a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online
identity headers on
since 2026-08-30T05:21:53+01:00
pid 2617173
callers outside your fleet get a 403. Ctrl-C stops it.
A caller outside the fleet gets exactly that, with a reason rather than a bare status line:
$ curl https://a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online/
this port is served to its fleet only, and 2001:db8:24d:5d00::dedb is not in it
Every request that does reach the origin carries who called, established by Whisper rather than claimed by the caller: Whisper-Client-Address always, and Whisper-Agent-Address/-FQDN/-Owner plus Whisper-Assess-Band/-Coverage when they could be proven. Every Whisper-* and Tailscale-* header the caller itself sent is stripped first, so an origin can trust Whisper-Agent-FQDN by its presence alone; --compat-headers also emits the Tailscale-User-* spellings, for an app mid-migration. whisper whale serve status shows what a host is serving; whisper whale serve off stops it immediately.
funnel is the same mechanism with the fleet check removed: the whole internet is answered, not just your fleet. Because the /128 is already globally routable there is no propagation wait and no certificate-authority rate limit to hit - going public is a decision, not a deployment, which is why it asks you to type yes (or pass --yes) rather than just doing it. One caveat: a caller that is itself a Whisper /128 in another tenant is still governed by the east-west plane above, so it can be refused before it ever reaches you while the rest of the internet is answered normally.
Both verbs serve on :443 because _443._tcp.<fqdn> TLSA is what makes the leaf verifiable, and both accept a target liberally - 3000, :3000, localhost:3000, [::1]:3000, http://127.0.0.1:3000/api all work. The three /.well-known/ identity paths stay Whisper's own and are never proxied through.
Installing, and removing
One command in, one command out, and the same pair on Linux and macOS:
curl -fsSL https://get.whisper.online | sh # install
curl -fsSL https://get.whisper.online/uninstall | sh # remove
On Windows:
irm https://get.whisper.online/install.ps1 | iex # install
irm https://get.whisper.online/uninstall.ps1 | iex # remove
macOS also has a signed and notarized installer package, for people who would rather double-click than paste a command. The uninstaller above knows that layout too: it removes the /opt/whisper payload, stops the host sensor through the CLI's own service uninstall, forgets the package receipt, and withdraws this machine's /128 first so nothing keeps routing after the software is gone.
Next
Connect & egress - the three tiers a single agent uses to put its traffic on the wire · Verify an agent - the same checks whale whois runs, done by hand with dig and curl.