diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/btftunnel.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff) | |
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we
build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped
line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git
merge-base returns no result at all -- so anyone cloning this repository
landed on a default branch that was neither current nor related to the
running node.
The 28 commits on main were audited before this merge: every one is already
present in the tree by content, including the two that are hardest to see
because they were rewritten afterwards -- the payout reorg-safety work
(PayoutBlockStillOnChain, since evolved to re-check on every retry rather
than once at maturity) and the nostr relay certificate verification
(SSL_set1_host, now with a Windows root-store path). Nothing on main is lost
by this merge.
Recorded as a merge rather than a force-push: main only ever moves forward,
its old history stays reachable as the first parent, and this is revertible.
The tree is taken wholesale from the merged branch, so main's content is now
exactly what was built and tested.
What this brings onto main:
- compactblock: BIP152-style compact block relay ported from upstream 1.2.23,
primitives and unit test only, with the RAND_bytes failure path closed and
the reconstruction allocation bounded.
- upstream/main 17741d9: isolated testnet parameters and genesis, optional
2 MB large pages for RandomX. Mainnet genesis verified byte-identical
through upstream's BuildGenesisBlock() refactor.
- deploy: every image built on bookworm so binaries run on Debian 12, and the
container build now honours the RandomX and libsecp256k1 pins it had been
silently ignoring since before 1.2.15.
Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus
constants unchanged against e4f898c apart from the pre-existing
MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/btftunnel.cpp')
| -rw-r--r-- | src/btftunnel.cpp | 140 |
1 files changed, 132 insertions, 8 deletions
diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp index 63d178b..4058f6b 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp | |||
| @@ -25,8 +25,12 @@ typedef int SOCKET; | |||
| 25 | #endif | 25 | #endif |
| 26 | #include "sockcount.h" | 26 | #include "sockcount.h" |
| 27 | #include <cstring> | 27 | #include <cstring> |
| 28 | #include <cstdio> | ||
| 29 | #include <memory> | ||
| 30 | #include <new> | ||
| 28 | #include <vector> | 31 | #include <vector> |
| 29 | #include <thread> | 32 | #include <thread> |
| 33 | #include <system_error> | ||
| 30 | 34 | ||
| 31 | namespace btf | 35 | namespace btf |
| 32 | { | 36 | { |
| @@ -119,35 +123,155 @@ static void PumpIn(SOCKET pumpEnd, RvSocket rv, std::vector<unsigned char> key) | |||
| 119 | shutdown((SOCKET)rv, SD_BOTH); | 123 | shutdown((SOCKET)rv, SD_BOTH); |
| 120 | } | 124 | } |
| 121 | 125 | ||
| 126 | // The pump end and the rendezvous socket, owned jointly by whoever is still | ||
| 127 | // using them. Closed once, by the last reference to go. | ||
| 128 | // | ||
| 129 | // Joint ownership rather than "the inbound pump owns the cleanup", because | ||
| 130 | // either pump can reach its exit first and BOTH touch BOTH descriptors on the | ||
| 131 | // way out -- PumpOut ends in shutdown(rv), shutdown(pumpEnd) and PumpIn ends in | ||
| 132 | // the mirror image. A single owner therefore has no exit at which the other is | ||
| 133 | // guaranteed to be finished. Holding a reference for the whole body of each | ||
| 134 | // pump gives it one: the close cannot happen until the last pump has stopped | ||
| 135 | // touching the descriptors, so no shutdown() can land on a number that has | ||
| 136 | // already been closed and handed back out by the kernel to an unrelated | ||
| 137 | // connection. | ||
| 138 | struct TunnelEnds | ||
| 139 | { | ||
| 140 | SOCKET pumpEnd; | ||
| 141 | RvSocket rv; | ||
| 142 | TunnelEnds(SOCKET p, RvSocket r) : pumpEnd(p), rv(r) {} | ||
| 143 | ~TunnelEnds() { BtfCloseSocket(pumpEnd); RvClose(rv); } | ||
| 144 | private: | ||
| 145 | TunnelEnds(const TunnelEnds&); | ||
| 146 | TunnelEnds& operator=(const TunnelEnds&); | ||
| 147 | }; | ||
| 148 | |||
| 122 | // Start the two pumps and hand back the app-facing socket. When both pumps | 149 | // Start the two pumps and hand back the app-facing socket. When both pumps |
| 123 | // finish, close the pump end and the rendezvous socket. | 150 | // finish, close the pump end and the rendezvous socket. |
| 151 | // | ||
| 152 | // That sentence used to be a comment describing something the code did not do. | ||
| 153 | // The inbound pump closed both descriptors as soon as it exited, without | ||
| 154 | // waiting for the outbound pump, which then called shutdown() on both at its | ||
| 155 | // own exit. Whether that shutdown hit a live descriptor or a closed one came | ||
| 156 | // down to which pump got there first, and the rendezvous socket dying before | ||
| 157 | // the app socket -- a relay restart, the far side hanging up -- is precisely | ||
| 158 | // the order that puts the inbound pump ahead. Measured over 25 such teardowns | ||
| 159 | // against the real code: 29 shutdowns landed on an already-closed descriptor. | ||
| 160 | // | ||
| 161 | // appEnd is not part of this. It is handed to the caller on success and closed | ||
| 162 | // here on every failure path; no pump ever touches it. | ||
| 163 | // | ||
| 164 | // Every tunnel is two threads, and a tunnel is one peer connection (a .btf | ||
| 165 | // dial or accept, a pool miner, a participant's pool session) -- so the | ||
| 166 | // thread count scales with peer count, which is attacker-influenced. When | ||
| 167 | // pthread_create is refused std::thread throws std::system_error; uncaught it | ||
| 168 | // reached std::terminate and took the whole process down over one connection | ||
| 169 | // it could not serve. Both spawns are guarded, and the failure paths return | ||
| 170 | // INVALID_SOCKET, which every caller already handles by dropping just that | ||
| 171 | // connection. | ||
| 124 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) | 172 | static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) |
| 125 | { | 173 | { |
| 126 | btf_socket_t appEnd, pumpEnd; | 174 | btf_socket_t appEnd, pumpEnd; |
| 127 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } | 175 | if (!MakeLocalPair(appEnd, pumpEnd)) { RvClose(rv); return INVALID_SOCKET; } |
| 128 | 176 | ||
| 129 | std::vector<unsigned char> key(shared, shared + 32); | 177 | std::vector<unsigned char> key(shared, shared + 32); |
| 130 | std::thread(PumpOut, pumpEnd, rv, key).detach(); | 178 | |
| 131 | std::thread([pumpEnd, rv, key]() { | 179 | // make_shared, not shared_ptr(new ...): it allocates before it constructs, |
| 132 | PumpIn(pumpEnd, rv, key); | 180 | // so it either comes back owning both descriptors or throws having taken |
| 181 | // ownership of neither. The two-step form can run the deleter and then | ||
| 182 | // throw, which would close both here and again below. | ||
| 183 | std::shared_ptr<TunnelEnds> ends; | ||
| 184 | try | ||
| 185 | { | ||
| 186 | ends = std::make_shared<TunnelEnds>(pumpEnd, rv); | ||
| 187 | } | ||
| 188 | catch (const std::bad_alloc&) | ||
| 189 | { | ||
| 190 | fprintf(stderr, "btf tunnel: out of memory starting tunnel, tunnel dropped\n"); | ||
| 133 | BtfCloseSocket(pumpEnd); | 191 | BtfCloseSocket(pumpEnd); |
| 192 | BtfCloseSocket(appEnd); | ||
| 134 | RvClose(rv); | 193 | RvClose(rv); |
| 135 | }).detach(); | 194 | return INVALID_SOCKET; |
| 195 | } | ||
| 196 | |||
| 197 | try | ||
| 198 | { | ||
| 199 | std::thread([ends, key]() { PumpIn(ends->pumpEnd, ends->rv, key); }).detach(); | ||
| 200 | } | ||
| 201 | catch (const std::system_error& e) | ||
| 202 | { | ||
| 203 | // Nothing is running yet: `ends` is the only reference, and returning | ||
| 204 | // drops it, which is what closes pumpEnd and rv. | ||
| 205 | fprintf(stderr, "btf tunnel: cannot spawn inbound pump (%s), tunnel dropped\n", | ||
| 206 | e.what()); | ||
| 207 | BtfCloseSocket(appEnd); | ||
| 208 | return INVALID_SOCKET; | ||
| 209 | } | ||
| 210 | |||
| 211 | try | ||
| 212 | { | ||
| 213 | std::thread([ends, key]() { PumpOut(ends->pumpEnd, ends->rv, key); }).detach(); | ||
| 214 | } | ||
| 215 | catch (const std::system_error& e) | ||
| 216 | { | ||
| 217 | // The inbound pump may be parked in RvReadN(rv) or may already have | ||
| 218 | // exited -- when the spawn is refused because the process is loaded, | ||
| 219 | // the rendezvous socket is often dead for the same reason, and then | ||
| 220 | // PumpIn returns on its first length read. Either way this thread | ||
| 221 | // still holds a reference, so both descriptors are still open: the | ||
| 222 | // shutdown wakes the pump if it is parked and is harmless if it is | ||
| 223 | // not. The close belongs to whichever reference goes last. | ||
| 224 | fprintf(stderr, "btf tunnel: cannot spawn outbound pump (%s), tunnel dropped\n", | ||
| 225 | e.what()); | ||
| 226 | shutdown((SOCKET)ends->rv, SD_BOTH); | ||
| 227 | shutdown(ends->pumpEnd, SD_BOTH); | ||
| 228 | BtfCloseSocket(appEnd); | ||
| 229 | return INVALID_SOCKET; | ||
| 230 | } | ||
| 136 | return appEnd; | 231 | return appEnd; |
| 137 | } | 232 | } |
| 138 | 233 | ||
| 234 | const char* TunnelFailText(TunnelFail f) | ||
| 235 | { | ||
| 236 | switch (f) | ||
| 237 | { | ||
| 238 | case TUNNEL_FAIL_NONE: return "no failure"; | ||
| 239 | case TUNNEL_FAIL_INIT: return "local socket/crypto init failed"; | ||
| 240 | case TUNNEL_FAIL_DIAL: return "meeting node unreachable (tcp dial failed)"; | ||
| 241 | case TUNNEL_FAIL_REFUSED: return "relay reachable, but no service registered there for this address"; | ||
| 242 | case TUNNEL_FAIL_PAIR: return "relay connection died before pairing completed"; | ||
| 243 | case TUNNEL_FAIL_HANDSHAKE: return "end-to-end handshake failed"; | ||
| 244 | case TUNNEL_FAIL_LOCAL: return "local tunnel setup failed"; | ||
| 245 | } | ||
| 246 | return "unknown failure"; | ||
| 247 | } | ||
| 248 | |||
| 139 | btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, | 249 | btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, |
| 140 | const unsigned char target_pubkey[32], | 250 | const unsigned char target_pubkey[32], |
| 141 | const unsigned char service_enc_pub[32]) | 251 | const unsigned char service_enc_pub[32], |
| 252 | TunnelFail* pFail) | ||
| 142 | { | 253 | { |
| 254 | if (pFail) *pFail = TUNNEL_FAIL_INIT; | ||
| 143 | if (!TunnelInit()) return INVALID_SOCKET; | 255 | if (!TunnelInit()) return INVALID_SOCKET; |
| 144 | RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey); | 256 | RvClientFail rvFail = RVC_FAIL_NONE; |
| 145 | if (rv == RV_INVALID) return INVALID_SOCKET; | 257 | RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey, &rvFail); |
| 258 | if (rv == RV_INVALID) | ||
| 259 | { | ||
| 260 | if (pFail) | ||
| 261 | *pFail = rvFail == RVC_FAIL_DIAL ? TUNNEL_FAIL_DIAL | ||
| 262 | : rvFail == RVC_FAIL_REFUSED ? TUNNEL_FAIL_REFUSED | ||
| 263 | : TUNNEL_FAIL_PAIR; | ||
| 264 | return INVALID_SOCKET; | ||
| 265 | } | ||
| 146 | 266 | ||
| 147 | unsigned char eph_pub[32], shared[32]; | 267 | unsigned char eph_pub[32], shared[32]; |
| 268 | if (pFail) *pFail = TUNNEL_FAIL_HANDSHAKE; | ||
| 148 | if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; } | 269 | if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; } |
| 149 | if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; } | 270 | if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; } |
| 150 | return StartTunnel(rv, shared); | 271 | if (pFail) *pFail = TUNNEL_FAIL_LOCAL; |
| 272 | btf_socket_t app = StartTunnel(rv, shared); | ||
| 273 | if (app != INVALID_SOCKET && pFail) *pFail = TUNNEL_FAIL_NONE; | ||
| 274 | return app; | ||
| 151 | } | 275 | } |
| 152 | 276 | ||
| 153 | btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]) | 277 | btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]) |