upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/nostr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr.cpp')
-rw-r--r--src/nostr.cpp141
1 files changed, 82 insertions, 59 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 3f43302..32cea18 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -7,6 +7,10 @@
7#include <openssl/ssl.h> 7#include <openssl/ssl.h>
8#include <openssl/err.h> 8#include <openssl/err.h>
9#include <openssl/x509.h> 9#include <openssl/x509.h>
10#include <openssl/x509v3.h>
11#ifdef _WIN32
12#include <wincrypt.h>
13#endif
10// We link libsecp256k1 statically (without this the header assumes DLL import). 14// We link libsecp256k1 statically (without this the header assumes DLL import).
11// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from 15// Both makefiles also pass -DSECP256K1_STATIC; the guard keeps that from
12// warning while leaving this file correct if built without the flag. 16// warning while leaving this file correct if built without the flag.
@@ -26,6 +30,7 @@
26 30
27#include "btfaddr.h" 31#include "btfaddr.h"
28#include "btfchan.h" 32#include "btfchan.h"
33#include "proxy.h"
29 34
30using json = nlohmann::json; 35using json = nlohmann::json;
31 36
@@ -468,6 +473,41 @@ static bool BuildSignedEvent(CNostrKey& key, int kind, const json& tags,
468static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame 473static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame
469static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled 474static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled
470 475
476static int BtfConnectedNodeCount()
477{
478 int nNodes = 0;
479 CRITICAL_BLOCK(cs_vNodes)
480 {
481 nNodes = (int)vNodes.size();
482 }
483 return nNodes;
484}
485
486#ifdef _WIN32
487static bool BtfLoadWindowsRootCertificates(SSL_CTX* sslctx)
488{
489 HCERTSTORE hStore = CertOpenSystemStoreA(0, "ROOT");
490 if (!hStore)
491 return false;
492
493 bool fLoaded = false;
494 X509_STORE* x509store = SSL_CTX_get_cert_store(sslctx);
495 PCCERT_CONTEXT pCert = NULL;
496 while ((pCert = CertEnumCertificatesInStore(hStore, pCert)) != NULL)
497 {
498 const unsigned char* p = pCert->pbCertEncoded;
499 X509* x509 = d2i_X509(NULL, &p, pCert->cbCertEncoded);
500 if (!x509)
501 continue;
502 if (X509_STORE_add_cert(x509store, x509) == 1)
503 fLoaded = true;
504 X509_free(x509);
505 }
506 CertCloseStore(hStore, 0);
507 return fLoaded;
508}
509#endif
510
471class CWebSocket 511class CWebSocket
472{ 512{
473public: 513public:
@@ -523,53 +563,8 @@ public:
523 size_t colon = host.find(':'); 563 size_t colon = host.find(':');
524 if (colon != string::npos) { port = atoi(host.substr(colon + 1).c_str()); host = host.substr(0, colon); } 564 if (colon != string::npos) { port = atoi(host.substr(colon + 1).c_str()); host = host.substr(0, colon); }
525 565
526 // Resolve and connect (TCP). AF_UNSPEC so we get both A and AAAA 566 hSocket = BtfConnectSocket(host, (unsigned short)port, SOCK_NOSTR, timeoutSec);
527 // records back and try them in the order the resolver prefers, 567 if (hSocket == INVALID_SOCKET)
528 // rather than hard-failing whenever the IPv4 path to a relay is
529 // filtered/rate-limited (increasingly common for CDN-fronted
530 // relays) while a working IPv6 route exists right there in the
531 // same getaddrinfo() result.
532 struct addrinfo hints, *res = NULL, *rp = NULL;
533 memset(&hints, 0, sizeof(hints));
534 hints.ai_family = AF_UNSPEC;
535 hints.ai_socktype = SOCK_STREAM;
536 char portstr[16]; sprintf(portstr, "%d", port);
537 if (getaddrinfo(host.c_str(), portstr, &hints, &res) != 0 || !res)
538 return error("Nostr: getaddrinfo %s failed", host.c_str());
539
540 bool fConnected = false;
541 for (rp = res; rp != NULL; rp = rp->ai_next)
542 {
543 hSocket = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), SOCK_NOSTR);
544 if (hSocket == INVALID_SOCKET)
545 continue;
546
547 // Socket receive/send timeout: Windows takes a DWORD of
548 // milliseconds; POSIX takes a struct timeval.
549#ifdef _WIN32
550 DWORD tv = timeoutSec * 1000;
551 setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
552 setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
553#else
554 struct timeval tv;
555 tv.tv_sec = timeoutSec;
556 tv.tv_usec = 0;
557 setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
558 setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
559#endif
560
561 if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
562 {
563 fConnected = true;
564 break;
565 }
566
567 BtfCloseSocket(hSocket);
568 hSocket = INVALID_SOCKET;
569 }
570 freeaddrinfo(res);
571
572 if (!fConnected)
573 return error("Nostr: connect %s:%d failed", host.c_str(), port); 568 return error("Nostr: connect %s:%d failed", host.c_str(), port);
574 569
575 // TLS 570 // TLS
@@ -585,22 +580,35 @@ public:
585 // so a MITM cannot forge them, but it could drop the rendezvous 580 // so a MITM cannot forge them, but it could drop the rendezvous
586 // registration (leaving the pool unreachable to miners) or answer 581 // registration (leaving the pool unreachable to miners) or answer
587 // subscriptions with whatever it liked. 582 // subscriptions with whatever it liked.
588 if (!SSL_CTX_set_default_verify_paths(sslctx))
589 return error("Nostr: no system CA store available, refusing %s", host.c_str());
590 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL); 583 SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL);
584
585 // OpenSSL's default verify paths are usually empty on Windows, so
586 // the system ROOT store is loaded too -- without it verification
587 // fails closed there and every relay becomes unreachable.
588 bool fTrustLoaded = (SSL_CTX_set_default_verify_paths(sslctx) == 1);
589#ifdef _WIN32
590 fTrustLoaded = BtfLoadWindowsRootCertificates(sslctx) || fTrustLoaded;
591#endif
592 if (!fTrustLoaded)
593 return error("Nostr: failed to load TLS trust store");
594
595 // Floor the protocol version. Verification says who the peer is,
596 // not how weak a cipher suite we will agree to; without this a
597 // relay offering nothing better still gets TLS 1.0.
591 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION); 598 SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION);
592 599
593 ssl = SSL_new(sslctx); 600 ssl = SSL_new(sslctx);
594 if (!ssl) return false; 601 if (!ssl) return false;
595 SSL_set_fd(ssl, (int)hSocket); 602 SSL_set_fd(ssl, (int)hSocket);
596 SSL_set_tlsext_host_name(ssl, host.c_str()); // SNI 603 if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) // SNI
604 return error("Nostr: failed to set TLS SNI for %s", host.c_str());
597 605
598 // Verifying the chain is not enough on its own: without this the 606 // Verifying the chain is not enough on its own: without this the
599 // relay could present any certificate a trusted CA ever issued, 607 // relay could present any certificate a trusted CA ever issued,
600 // for any name. SSL_set1_host binds the check to the host we asked 608 // for any name. SSL_set1_host binds the check to the host we asked
601 // for. 609 // for.
602 if (!SSL_set1_host(ssl, host.c_str())) 610 if (SSL_set1_host(ssl, host.c_str()) != 1)
603 return error("Nostr: cannot pin verification to %s", host.c_str()); 611 return error("Nostr: failed to set TLS hostname for %s", host.c_str());
604 612
605 if (SSL_connect(ssl) != 1) 613 if (SSL_connect(ssl) != 1)
606 { 614 {
@@ -1071,6 +1079,7 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
1071 json filter = json::object(); 1079 json filter = json::object();
1072 filter["authors"] = json::array({ HexEncode(pk, 32) }); 1080 filter["authors"] = json::array({ HexEncode(pk, 32) });
1073 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1081 filter["kinds"] = json::array({ BTF_DESC_KIND });
1082 filter["#d"] = json::array({ BTF_DESC_DTAG });
1074 filter["limit"] = 1; 1083 filter["limit"] = 1;
1075 json req = json::array({ "REQ", "btf-resolve", filter }); 1084 json req = json::array({ "REQ", "btf-resolve", filter });
1076 if (!ws.SendText(req.dump())) 1085 if (!ws.SendText(req.dump()))
@@ -1207,6 +1216,7 @@ bool BtfResolveMany(const std::vector<std::string>& btfAddrs,
1207 json filter = json::object(); 1216 json filter = json::object();
1208 filter["authors"] = authors; 1217 filter["authors"] = authors;
1209 filter["kinds"] = json::array({ BTF_DESC_KIND }); 1218 filter["kinds"] = json::array({ BTF_DESC_KIND });
1219 filter["#d"] = json::array({ BTF_DESC_DTAG });
1210 filter["limit"] = (int)authors.size(); 1220 filter["limit"] = (int)authors.size();
1211 json req = json::array({ "REQ", "btf-resolve-many", filter }); 1221 json req = json::array({ "REQ", "btf-resolve-many", filter });
1212 if (!ws.SendText(req.dump())) 1222 if (!ws.SendText(req.dump()))
@@ -1617,8 +1627,9 @@ static void ConnectDiscoveredBtfPeers()
1617 // plus a random sample, not one or the other. 1627 // plus a random sample, not one or the other.
1618 int64 now = GetTime(); 1628 int64 now = GetTime();
1619 int nBudget = (int)BTF_DIALS_PER_PASS; 1629 int nBudget = (int)BTF_DIALS_PER_PASS;
1630 int nNodeCount = BtfConnectedNodeCount();
1620 { 1631 {
1621 int nRoom = (int)BTF_TARGET_CONN - (int)vNodes.size(); 1632 int nRoom = (int)BTF_TARGET_CONN - nNodeCount;
1622 if (nRoom < nBudget) nBudget = nRoom; 1633 if (nRoom < nBudget) nBudget = nRoom;
1623 } 1634 }
1624 if (nBudget < 0) nBudget = 0; 1635 if (nBudget < 0) nBudget = 0;
@@ -1632,7 +1643,6 @@ static void ConnectDiscoveredBtfPeers()
1632 // Reliability slice: walk the tiered (good -> untried -> bad) order. 1643 // Reliability slice: walk the tiered (good -> untried -> bad) order.
1633 for (const string& addr : peers) 1644 for (const string& addr : peers)
1634 { 1645 {
1635 if ((int)vNodes.size() >= (int)BTF_TARGET_CONN) break;
1636 if ((int)candidates.size() >= nSortedSlots) break; 1646 if ((int)candidates.size() >= nSortedSlots) break;
1637 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr); 1647 map<string, int64>::iterator bi = g_btfPeerBackoff.find(addr);
1638 if (bi != g_btfPeerBackoff.end() && now < bi->second) 1648 if (bi != g_btfPeerBackoff.end() && now < bi->second)
@@ -1657,7 +1667,7 @@ static void ConnectDiscoveredBtfPeers()
1657 int nWant = min(nRandomSlots, (int)pool.size()); 1667 int nWant = min(nRandomSlots, (int)pool.size());
1658 for (int i = 0; i < nWant; i++) 1668 for (int i = 0; i < nWant; i++)
1659 { 1669 {
1660 if ((int)vNodes.size() + (int)candidates.size() >= (int)BTF_TARGET_CONN) break; 1670 if (nNodeCount + (int)candidates.size() >= (int)BTF_TARGET_CONN) break;
1661 if ((int)candidates.size() >= nBudget) break; 1671 if ((int)candidates.size() >= nBudget) break;
1662 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i)); 1672 size_t j = i + (size_t)GetRand((uint64)(pool.size() - i));
1663 std::swap(pool[i], pool[j]); 1673 std::swap(pool[i], pool[j]);
@@ -1712,12 +1722,10 @@ static void ConnectDiscoveredBtfPeers()
1712 // not the shared Nostr relays, so there's no thundering-herd effect here. 1722 // not the shared Nostr relays, so there's no thundering-herd effect here.
1713 // Each thread writes its result back via shared state guarded by cs_btfPeerState. 1723 // Each thread writes its result back via shared state guarded by cs_btfPeerState.
1714 struct DialCtx { string addr; BtfResolvedPeer rp; }; 1724 struct DialCtx { string addr; BtfResolvedPeer rp; };
1715 vector<DialCtx*> ctxs;
1716 for (const string& addr : toDial) 1725 for (const string& addr : toDial)
1717 { 1726 {
1718 DialCtx* ctx = new DialCtx{addr, resolved[addr]}; 1727 DialCtx* ctx = new DialCtx{addr, resolved[addr]};
1719 ctxs.push_back(ctx); 1728 uintptr_t tid = _beginthread([](void* arg) {
1720 _beginthread([](void* arg) {
1721 DialCtx* ctx = (DialCtx*)arg; 1729 DialCtx* ctx = (DialCtx*)arg;
1722 string addr = ctx->addr; 1730 string addr = ctx->addr;
1723 string meeting = ctx->rp.meetingHostPort; 1731 string meeting = ctx->rp.meetingHostPort;
@@ -1750,6 +1758,21 @@ static void ConnectDiscoveredBtfPeers()
1750 } 1758 }
1751 } 1759 }
1752 }, 0, ctx); 1760 }, 0, ctx);
1761 if (tid == (uintptr_t)-1)
1762 {
1763 LogPrint("nostr", "Nostr: failed to create .btf dial thread for %s\n", addr.c_str());
1764 CRITICAL_BLOCK(cs_btfPeerState)
1765 {
1766 int n = ++g_btfPeerFails[addr];
1767 int64 cap = (n >= BTF_CHRONIC_FAIL_THRESHOLD) ? BTF_BACKOFF_CAP_SECS_DEAD
1768 : BTF_BACKOFF_CAP_SECS;
1769 int64 secs = 30;
1770 for (int k = 1; k < n && secs < cap; k++) secs *= 2;
1771 if (secs > cap) secs = cap;
1772 g_btfPeerBackoff[addr] = GetTime() + secs;
1773 }
1774 delete ctx;
1775 }
1753 } 1776 }
1754 1777
1755 // Wait long enough for all parallel dials to finish (each has a 10s socket 1778 // Wait long enough for all parallel dials to finish (each has a 10s socket
@@ -1852,7 +1875,7 @@ void ThreadNostrSeed(void* parg)
1852 1875
1853 // Re-discover periodically. Sleep less aggressively when 1876 // Re-discover periodically. Sleep less aggressively when
1854 // we have no connections yet so we find peers faster on startup. 1877 // we have no connections yet so we find peers faster on startup.
1855 int nSleepSecs = vNodes.empty() ? 20 : 60; 1878 int nSleepSecs = (BtfConnectedNodeCount() == 0) ? 20 : 60;
1856 for (int i = 0; i < nSleepSecs && !fShutdown; i++) 1879 for (int i = 0; i < nSleepSecs && !fShutdown; i++)
1857 Sleep(1000); 1880 Sleep(1000);
1858 } 1881 }