Skip to content

Releases: jjacke13/holesail-cpp

v0.1.1 — five transport fixes, plus an x86-64 static build

Choose a tag to compare

@jjacke13 jjacke13 released this 01 Aug 11:03

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 holesail 2.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 --lookup against 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

Choose a tag to compare

@jjacke13 jjacke13 released this 28 Jul 09:33

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.