From 3efd9a089cc6b924d9bc917d3d8145f7c01265ad Mon Sep 17 00:00:00 2001 From: Bitflash-sh Date: Fri, 24 Jul 2026 12:13:30 -0300 Subject: Multi-relay rendezvous with failover + relay rate-limiting Seed list of 3 geographically-spread relays (Sao Paulo, New Jersey, Almaty); nodes register at the first reachable one and fail over if it is attacked or goes down, so no single relay IP is a point of failure. Relay installer now applies iptables connection rate-limits to soak up single-source floods. --- relay/install-bitflash-relay.sh | 19 +++++++++++++++++++ src/headless.cpp | 2 +- src/net.cpp | 21 +++++++++++++++++---- src/nostr.cpp | 32 +++++++++++++++++++++++++++++--- src/nostr.h | 15 ++++++++++++--- src/ui.cpp | 6 +++--- 6 files changed, 81 insertions(+), 14 deletions(-) diff --git a/relay/install-bitflash-relay.sh b/relay/install-bitflash-relay.sh index 77a46b2..fd901ac 100644 --- a/relay/install-bitflash-relay.sh +++ b/relay/install-bitflash-relay.sh @@ -295,6 +295,25 @@ echo ">> building bitflashd" g++ -std=gnu++14 -O2 -pthread bitflashd.cpp btfrv.cpp -o bitflashd echo ">> build OK: $DIR/bitflashd" +# Basic anti-flood firewall rules (idempotent). These soak up single-source +# connection floods; volumetric DDoS still needs provider-level protection. +# Limits are generous so many users behind one CGNAT public IP aren't blocked. +if command -v iptables >/dev/null 2>&1; then + echo ">> applying connection rate-limit on port $PORT" + # Cap concurrent connections per source IP (stops one IP hogging thousands) + iptables -C INPUT -p tcp --dport "$PORT" -m connlimit --connlimit-above 200 --connlimit-mask 32 -j REJECT --reject-with tcp-reset 2>/dev/null || \ + iptables -A INPUT -p tcp --dport "$PORT" -m connlimit --connlimit-above 200 --connlimit-mask 32 -j REJECT --reject-with tcp-reset + # Cap NEW-connection rate per source IP (stops connection floods) + iptables -C INPUT -p tcp --dport "$PORT" -m conntrack --ctstate NEW -m hashlimit \ + --hashlimit-name btfrelay --hashlimit-mode srcip --hashlimit-above 60/min --hashlimit-burst 100 -j DROP 2>/dev/null || \ + iptables -A INPUT -p tcp --dport "$PORT" -m conntrack --ctstate NEW -m hashlimit \ + --hashlimit-name btfrelay --hashlimit-mode srcip --hashlimit-above 60/min --hashlimit-burst 100 -j DROP + # Persist across reboots if the tooling is present + command -v netfilter-persistent >/dev/null 2>&1 && netfilter-persistent save >/dev/null 2>&1 || true +else + echo ">> iptables not found; skipping rate-limit (host on a provider with anti-DDoS)" +fi + # systemd service so it survives reboots (skipped if systemd is absent) if command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then cat > /etc/systemd/system/bitflashd.service < try the next one + Sleep(3000); continue; } + // Registered OK: advertise THIS relay in our descriptor so clients dial + // us here, and keep using it (iRelay unchanged) until it fails. + BtfSetActiveRelay(strMeeting); btf_socket_t hSocket = btf::BtfServiceWrap(rv, sk); if (hSocket == INVALID_SOCKET) continue; @@ -1146,7 +1159,7 @@ bool StartNode(string& strError) // Anonymous inbound: this node's .btf hidden service, reachable through the // meeting relay without exposing our IP or needing a public port. - if (!strBtfMeetingRelay.empty()) + if (!vBtfMeetingRelays.empty()) if (_beginthread(ThreadBtfAccept, 0, NULL) == -1) printf("Error: _beginthread(ThreadBtfAccept) failed\n"); diff --git a/src/nostr.cpp b/src/nostr.cpp index 8e2a07d..59817f5 100644 --- a/src/nostr.cpp +++ b/src/nostr.cpp @@ -55,7 +55,32 @@ const int nNostrRelays = ARRAYLEN(pszNostrRelays); // Rendezvous meeting relay this node registers at (ThreadBtfAccept in net.cpp) // and advertises in its .btf descriptor. Seed default; /rvrelay overrides. -string strBtfMeetingRelay = "90.156.222.107:8434"; +// Seed rendezvous relays. More entries = more resilience: the node fails over +// between them (see ThreadBtfAccept), so DDoSing one relay IP can't take the +// network down. /rvrelay overrides this list with a single entry. +vector vBtfMeetingRelays = { + "92.246.128.180:8434", // Sao Paulo, BR + "31.44.4.249:8434", // New Jersey, US + "90.156.222.107:8434", // Almaty, KZ +}; +string strBtfActiveRelay; +static CCriticalSection cs_activeRelay; + +string BtfActiveRelay() +{ + CRITICAL_BLOCK(cs_activeRelay) + if (!strBtfActiveRelay.empty()) + return strBtfActiveRelay; + if (!vBtfMeetingRelays.empty()) + return vBtfMeetingRelays[0]; + return ""; +} + +void BtfSetActiveRelay(const string& relay) +{ + CRITICAL_BLOCK(cs_activeRelay) + strBtfActiveRelay = relay; +} // @@ -519,8 +544,9 @@ static void PublishDescriptor(CWebSocket& ws, CNostrKey& key) { // meeting_node is the rendezvous relay this node's hidden service // (ThreadBtfAccept in net.cpp) is registered at -- where clients dial us. - string meeting_node = strBtfMeetingRelay.empty() ? "rendezvous-pending" - : strBtfMeetingRelay; + string meeting_node = BtfActiveRelay(); + if (meeting_node.empty()) + meeting_node = "rendezvous-pending"; string desc = btf::SignDescriptor(key.ctx, key.seckey, key.EncPubHex(), meeting_node, (uint64_t)GetTime()); if (desc.empty()) diff --git a/src/nostr.h b/src/nostr.h index 5ec9f65..41e59df 100644 --- a/src/nostr.h +++ b/src/nostr.h @@ -11,6 +11,7 @@ #define BITFLASH_NOSTR_H #include +#include // Thread entry point (signature compatible with _beginthread) void ThreadNostrSeed(void* parg); @@ -19,9 +20,17 @@ void ThreadNostrSeed(void* parg); extern const char* pszNostrRelays[]; extern const int nNostrRelays; -// Rendezvous meeting relay ("host:port") this node registers at and advertises -// in its .btf descriptor. Overridable with /rvrelay=host:port. -extern std::string strBtfMeetingRelay; +// Rendezvous meeting relays ("host:port") this node can register its .btf +// service at. It registers at the first reachable one and advertises that in +// its descriptor; if the relay dies it fails over to another, so attacking a +// single relay's IP can't take the network down. Overridable with /rvrelay. +extern std::vector vBtfMeetingRelays; +// The relay this node is currently registered at (set by ThreadBtfAccept). +extern std::string strBtfActiveRelay; +// The active relay, or the first seed if none active yet ("" if the list is empty). +std::string BtfActiveRelay(); +// Record the relay this node just registered its service at (thread-safe). +void BtfSetActiveRelay(const std::string& relay); // Copy this node's .btf identity (lazily loading or generating it): the x-only // pubkey (= the .btf address) and the x25519 secret for the end-to-end channel. diff --git a/src/ui.cpp b/src/ui.cpp index 3e734a5..cb1a94d 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2969,10 +2969,10 @@ bool CMyApp::OnInit2() if (mapArgs.count("/proxy")) addrProxy = CAddress(mapArgs["/proxy"].c_str()); - // Rendezvous meeting relay this node registers its .btf service at and - // advertises in its descriptor (default is the seed relay; empty disables). + // Rendezvous meeting relay override: replace the seed list with a single + // relay (default keeps the built-in seed list with failover). if (mapArgs.count("/rvrelay")) - strBtfMeetingRelay = mapArgs["/rvrelay"]; + vBtfMeetingRelays = { mapArgs["/rvrelay"] }; // Keep an anonymous outbound connection to a specific .btf peer. if (mapArgs.count("/connectbtf")) -- cgit v1.2.3