diff options
Diffstat (limited to 'src/nostr.cpp')
| -rw-r--r-- | src/nostr.cpp | 141 |
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 | ||
| 30 | using json = nlohmann::json; | 35 | using json = nlohmann::json; |
| 31 | 36 | ||
| @@ -468,6 +473,41 @@ static bool BuildSignedEvent(CNostrKey& key, int kind, const json& tags, | |||
| 468 | static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame | 473 | static const uint64 MAX_WS_FRAME = 1 << 20; // 1 MiB per frame |
| 469 | static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled | 474 | static const uint64 MAX_WS_MESSAGE = 4 << 20; // 4 MiB reassembled |
| 470 | 475 | ||
| 476 | static 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 | ||
| 487 | static 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 | |||
| 471 | class CWebSocket | 511 | class CWebSocket |
| 472 | { | 512 | { |
| 473 | public: | 513 | public: |
| @@ -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 | } |