Releases: jjacke13/holesail-cpp
Release list
v0.1.1 — five transport fixes, plus an x86-64 static build
A bug-fix release. If you are running v0.1.0, upgrade — it carries five
transport defects, one of which is a crash.
All of them live in the hyperdht-cpp dependency rather than in this port, and
all are fixed by bumping it to 81974c0. The C API surface is byte-identical
across that range, so nothing in holesail-cpp had to change to pick them up.
What was broken in v0.1.0
| Symptom | |
|---|---|
| Relay set was write-once | Once three relay slots filled they were pinned for the life of the process. A relay that died — or that was never reachable from third parties, e.g. behind a symmetric NAT — was advertised to every client forever, and no healthier node could replace it. Restarting the server was the only cure. This is exactly the long-lived --live daemon workload. |
| Double stream destroy → crash | A second udx_stream_destroy() inside the DESTROYING-but-not-CLOSED window ran the close path twice and tripped libuv's assert(!uv__is_closing). It only bites in Release, where libudx's guarding assert is compiled out — and Release is what ships. Seen in the field as a SIGABRT during connect-failure teardown. |
| No announce-relay failover | The client picked one of the server's announced relays and treated its failure as terminal. One relay behind an endpoint-dependent NAT meant every connect failed -5 permanently, even with other healthy relays announced. |
| Local addresses were a bind-time snapshot | Any interface appearing after bind — DHCP completing under an early-starting service, an interface flap, an address renewal — was never advertised for the rest of the process lifetime, and same-LAN clients failed -6 permanently. Matters most when holesail runs as a boot-time unit. |
pause() / resume() left the server silent |
The announcer's alive_ sentinel was cleared on stop and never re-armed on start, so a suspended-then-resumed server never re-announced again. Reachable through this port's own Holesail::pause() / Holesail::resume(). |
Verification
- 11/11 unit suites.
- 6/6 live interop against the real JS
holesail2.4.1 binary on the public
DHT: C++↔C++, C++ server↔JS client and JS server↔C++ client, each in secure
and public mode, plus--lookupagainst a JS-published record. Real HTTP
through the tunnel, 200 with the body verified in every case.
Wire compatibility with upstream holesail is unchanged: same connection
strings, same key derivation, same DHT records.
Also in this release
--version now reports the real version. All four version strings in the tree
said 0.1.0; with a known-bad v0.1.0 still downloadable, the first question
about any binary in the field is which build it is, and the CLI has to be able
to answer.
A note this repo had recorded as fact was also retracted after upstream
re-tested it: hyperdht_query_cancel() does fire on_done
(HYPERDHT_ERR_CANCELLED), possibly synchronously, and the associated leak is
~1272 B rather than the ~34 KB previously claimed. No behaviour change here —
HolesailClient::destroy() already nulled its query handle before cancelling,
which is what prevents a double free — but that ordering is now documented as
load-bearing instead of looking incidental.
Install
x86-64 Linux — new in this release. v0.1.0 shipped an aarch64 binary only,
so an ordinary server or desktop had to build from source through nix.
curl -LO https://github.com/jjacke13/holesail-cpp/releases/download/v0.1.1/holesail-v0.1.1-x86_64-linux-static
chmod +x holesail-v0.1.1-x86_64-linux-static
./holesail-v0.1.1-x86_64-linux-static --version
aarch64 Linux (Raspberry Pi class):
curl -LO https://github.com/jjacke13/holesail-cpp/releases/download/v0.1.1/holesail-v0.1.1-aarch64-linux-static
chmod +x holesail-v0.1.1-aarch64-linux-static
./holesail-v0.1.1-aarch64-linux-static --version
Both are musl, fully static, with no runtime dependencies at all — no nix, no
Node, no libuv or libsodium on the target. ~3.5 MB on disk, ~3.2 MB RSS on a
Pi 5. Verify with SHA256SUMS.txt.
The x86-64 build was validated by running the interop harness with the static
binary itself — musl replaces glibc's resolver and threading, which is
precisely where a DHT can pass every local test and still fail on a real
network. It didn't: 6/6.
Note for hyperdht-cpp users
If you build on hyperdht-cpp's C FFI, these fixes matter to you too, and
one of them is a crash that only appears in Release builds. Separately, if you
are on anything older than 0203820, the C API firewall was inverted —
private servers admitted exactly the peers they should have rejected.
holesail-cpp is an unofficial, independent C++ port of
holesail by supersuryaansh. Use the
original unless you specifically need the smaller footprint.
v0.1.0 — first release
Warning
Superseded by v0.1.1 — do not use this release.
Five transport defects were found in the hyperdht-cpp dependency after this
was published. One is a crash: a double stream-destroy that trips libuv's
assert(!uv__is_closing), and it only bites in Release builds — which is what
the binary below is. The others cause permanent, non-recovering failures: a
write-once relay set that leaves a long-running server unreachable until it is
restarted, no announce-relay failover (-5 forever behind an
endpoint-dependent NAT), a bind-time address snapshot that breaks same-LAN
clients (-6 forever), and a pause()/resume() cycle that leaves the server
permanently silent.
This release is kept published only so the history stays honest.
A C++20 port of holesail 2.4.1 — peer-to-peer TCP/UDP tunnelling over HyperDHT — for hardware where a Node runtime is too heavy.
This is an unofficial, independent port. The protocol, the connection-string scheme and the product are the work of the upstream Holesail project. Use the original unless you specifically need the small footprint; it has features this does not (--filemanager, the desktop and mobile apps, QR pairing) and it is what the Holesail team supports.
Wire-compatible with the original
Same connection strings, key derivation and DHT records. Verified against the real JS 2.4.1 binary on the public DHT — C++↔C++, C++ server ↔ JS client, JS server ↔ C++ client, in both secure and public mode, plus --lookup against a JS-published record. scripts/cross-test.sh is that gate.
The official Holesail phone app connects to a C++ server over mobile data, across carrier NAT.
Footprint
Both idle on the same Raspberry Pi 5, same port, key and mode:
| holesail-cpp | holesail 2.4.1 (Node 24) | ||
|---|---|---|---|
| Resident | 3.2 MB | 77.8 MB | 24× smaller |
Real heap (Pss_Anon) |
0.7 MB | 27.9 MB | 40× smaller |
| Virtual | 3.5 MB | 1.17 GB | |
| Threads | 1 | 7 |
nix build .#holesail-aarch64-static gives a 3.4 MB static binary — scp it onto any aarch64 Linux box, Pi Zero 2 W upward, with zero runtime dependencies.
Scope
--live, --connect, --lookup, TCP and framed UDP, library plus CLI. --help is byte-identical to the JS binary apart from --filemanager.
Not ported: --filemanager, the cli-box output frame, the terminal QR code. Divergences (D1–D5) and reproduced upstream quirks (Q1–Q4) are documented in the README — including one deliberate fix: client --public means public mode, where JS made it mean the opposite of its own --help.
Note for hyperdht-cpp users
Building this surfaced a security bug in hyperdht-cpp: its C-API firewall wrapper inverted its return value, so every private server built on the C FFI admitted exactly the peers it should have refused, and refused the authorised one. Fixed upstream with regression tests. Consumers of the C FFI firewall should update. The C++ API was never affected.
Testing
81 unit cases, 4 network-gated loopback tunnels, clean under ASan and UBSan.
AGPL-3.0-or-later, matching upstream holesail.