diff options
| -rw-r--r-- | src/btfrv.cpp | 12 | ||||
| -rw-r--r-- | src/btftunnel.cpp | 23 | ||||
| -rw-r--r-- | src/net.cpp | 43 | ||||
| -rw-r--r-- | src/net.h | 2 | ||||
| -rw-r--r-- | src/nostr.cpp | 6 | ||||
| -rw-r--r-- | src/rpc.cpp | 4 | ||||
| -rw-r--r-- | src/sockcount.h | 137 |
7 files changed, 199 insertions, 28 deletions
diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 7efd38b..6ac3280 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #ifdef _WIN32 | 11 | #ifdef _WIN32 |
| 12 | #include <winsock2.h> | 12 | #include <winsock2.h> |
| 13 | #include <ws2tcpip.h> | 13 | #include <ws2tcpip.h> |
| 14 | #define CLOSESOCK closesocket | 14 | #define CLOSESOCK BtfCloseSocket |
| 15 | #else | 15 | #else |
| 16 | #include <sys/socket.h> | 16 | #include <sys/socket.h> |
| 17 | #include <netinet/in.h> | 17 | #include <netinet/in.h> |
| @@ -22,12 +22,14 @@ | |||
| 22 | typedef int SOCKET; | 22 | typedef int SOCKET; |
| 23 | #define INVALID_SOCKET (-1) | 23 | #define INVALID_SOCKET (-1) |
| 24 | #define SOCKET_ERROR (-1) | 24 | #define SOCKET_ERROR (-1) |
| 25 | #define CLOSESOCK ::close | 25 | #define CLOSESOCK BtfCloseSocket |
| 26 | #ifndef SD_BOTH | 26 | #ifndef SD_BOTH |
| 27 | #define SD_BOTH SHUT_RDWR | 27 | #define SD_BOTH SHUT_RDWR |
| 28 | #endif | 28 | #endif |
| 29 | #endif | 29 | #endif |
| 30 | 30 | ||
| 31 | #include "sockcount.h" | ||
| 32 | |||
| 31 | #include <cstring> | 33 | #include <cstring> |
| 32 | #include <cstdio> | 34 | #include <cstdio> |
| 33 | #include <string> | 35 | #include <string> |
| @@ -96,7 +98,7 @@ static SOCKET ConnectTo(const char* host, unsigned short port) | |||
| 96 | hints.ai_socktype = SOCK_STREAM; | 98 | hints.ai_socktype = SOCK_STREAM; |
| 97 | char portstr[16]; sprintf(portstr, "%u", (unsigned)port); | 99 | char portstr[16]; sprintf(portstr, "%u", (unsigned)port); |
| 98 | if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET; | 100 | if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET; |
| 99 | SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); | 101 | SOCKET s = BtfSocketTag(socket(res->ai_family, res->ai_socktype, res->ai_protocol), SOCK_RV_DIAL); |
| 100 | if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; } | 102 | if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; } |
| 101 | 103 | ||
| 102 | // Apply a short timeout only for the connect() and initial handshake so an | 104 | // Apply a short timeout only for the connect() and initial handshake so an |
| @@ -201,7 +203,7 @@ bool RvRelayRun(unsigned short port) | |||
| 201 | if (!RvInit()) return false; | 203 | if (!RvInit()) return false; |
| 202 | g_stop = false; | 204 | g_stop = false; |
| 203 | 205 | ||
| 204 | g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | 206 | g_listen = BtfSocketTag(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), SOCK_RV_LISTEN); |
| 205 | if (g_listen == INVALID_SOCKET) return false; | 207 | if (g_listen == INVALID_SOCKET) return false; |
| 206 | int yes = 1; | 208 | int yes = 1; |
| 207 | setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); | 209 | setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); |
| @@ -218,7 +220,7 @@ bool RvRelayRun(unsigned short port) | |||
| 218 | { | 220 | { |
| 219 | struct sockaddr_in cli; | 221 | struct sockaddr_in cli; |
| 220 | socklen_t len = sizeof(cli); | 222 | socklen_t len = sizeof(cli); |
| 221 | SOCKET s = accept(g_listen, (struct sockaddr*)&cli, &len); | 223 | SOCKET s = BtfSocketTag(accept(g_listen, (struct sockaddr*)&cli, &len), SOCK_RV_ACCEPT); |
| 222 | if (s == INVALID_SOCKET) | 224 | if (s == INVALID_SOCKET) |
| 223 | { | 225 | { |
| 224 | if (g_stop) break; | 226 | if (g_stop) break; |
diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp index 65c4b89..63d178b 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp | |||
| @@ -23,6 +23,7 @@ typedef int SOCKET; | |||
| 23 | #define SD_BOTH SHUT_RDWR | 23 | #define SD_BOTH SHUT_RDWR |
| 24 | #endif | 24 | #endif |
| 25 | #endif | 25 | #endif |
| 26 | #include "sockcount.h" | ||
| 26 | #include <cstring> | 27 | #include <cstring> |
| 27 | #include <vector> | 28 | #include <vector> |
| 28 | #include <thread> | 29 | #include <thread> |
| @@ -39,7 +40,7 @@ bool TunnelInit() | |||
| 39 | // Returns app end and pump end; both are ordinary full-duplex TCP sockets. | 40 | // Returns app end and pump end; both are ordinary full-duplex TCP sockets. |
| 40 | static bool MakeLocalPair(btf_socket_t& appEnd, btf_socket_t& pumpEnd) | 41 | static bool MakeLocalPair(btf_socket_t& appEnd, btf_socket_t& pumpEnd) |
| 41 | { | 42 | { |
| 42 | SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | 43 | SOCKET listener = BtfSocketTag(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), SOCK_PAIR_LISTEN); |
| 43 | if (listener == INVALID_SOCKET) return false; | 44 | if (listener == INVALID_SOCKET) return false; |
| 44 | 45 | ||
| 45 | struct sockaddr_in addr; | 46 | struct sockaddr_in addr; |
| @@ -47,23 +48,23 @@ static bool MakeLocalPair(btf_socket_t& appEnd, btf_socket_t& pumpEnd) | |||
| 47 | addr.sin_family = AF_INET; | 48 | addr.sin_family = AF_INET; |
| 48 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | 49 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| 49 | addr.sin_port = 0; // ephemeral | 50 | addr.sin_port = 0; // ephemeral |
| 50 | if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) != 0) { closesocket(listener); return false; } | 51 | if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) != 0) { BtfCloseSocket(listener); return false; } |
| 51 | if (listen(listener, 1) != 0) { closesocket(listener); return false; } | 52 | if (listen(listener, 1) != 0) { BtfCloseSocket(listener); return false; } |
| 52 | 53 | ||
| 53 | #ifdef _WIN32 | 54 | #ifdef _WIN32 |
| 54 | int len = sizeof(addr); | 55 | int len = sizeof(addr); |
| 55 | #else | 56 | #else |
| 56 | socklen_t len = sizeof(addr); | 57 | socklen_t len = sizeof(addr); |
| 57 | #endif | 58 | #endif |
| 58 | if (getsockname(listener, (struct sockaddr*)&addr, &len) != 0) { closesocket(listener); return false; } | 59 | if (getsockname(listener, (struct sockaddr*)&addr, &len) != 0) { BtfCloseSocket(listener); return false; } |
| 59 | 60 | ||
| 60 | SOCKET a = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | 61 | SOCKET a = BtfSocketTag(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), SOCK_PAIR_APP); |
| 61 | if (a == INVALID_SOCKET) { closesocket(listener); return false; } | 62 | if (a == INVALID_SOCKET) { BtfCloseSocket(listener); return false; } |
| 62 | if (connect(a, (struct sockaddr*)&addr, sizeof(addr)) != 0) { closesocket(a); closesocket(listener); return false; } | 63 | if (connect(a, (struct sockaddr*)&addr, sizeof(addr)) != 0) { BtfCloseSocket(a); BtfCloseSocket(listener); return false; } |
| 63 | 64 | ||
| 64 | SOCKET b = accept(listener, NULL, NULL); | 65 | SOCKET b = BtfSocketTag(accept(listener, NULL, NULL), SOCK_PAIR_PUMP); |
| 65 | closesocket(listener); | 66 | BtfCloseSocket(listener); |
| 66 | if (b == INVALID_SOCKET) { closesocket(a); return false; } | 67 | if (b == INVALID_SOCKET) { BtfCloseSocket(a); return false; } |
| 67 | 68 | ||
| 68 | appEnd = a; | 69 | appEnd = a; |
| 69 | pumpEnd = b; | 70 | pumpEnd = b; |
| @@ -129,7 +130,7 @@ static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) | |||
| 129 | std::thread(PumpOut, pumpEnd, rv, key).detach(); | 130 | std::thread(PumpOut, pumpEnd, rv, key).detach(); |
| 130 | std::thread([pumpEnd, rv, key]() { | 131 | std::thread([pumpEnd, rv, key]() { |
| 131 | PumpIn(pumpEnd, rv, key); | 132 | PumpIn(pumpEnd, rv, key); |
| 132 | closesocket(pumpEnd); | 133 | BtfCloseSocket(pumpEnd); |
| 133 | RvClose(rv); | 134 | RvClose(rv); |
| 134 | }).detach(); | 135 | }).detach(); |
| 135 | return appEnd; | 136 | return appEnd; |
diff --git a/src/net.cpp b/src/net.cpp index 19893ee..7a13257 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -51,6 +51,33 @@ int64 nBlocksReceived = 0; | |||
| 51 | int64 nBlocksWithoutParent = 0; | 51 | int64 nBlocksWithoutParent = 0; |
| 52 | static int64 nNodeStartTime = 0; | 52 | static int64 nNodeStartTime = 0; |
| 53 | 53 | ||
| 54 | // --- Socket accounting (implementation lives in sockcount.h) -------------- | ||
| 55 | static const char* pszSockSite[SOCK_SITES] = { | ||
| 56 | "external-ip probe", "listen socket", "inbound accept", | ||
| 57 | "rendezvous dial", "rendezvous listen", "rendezvous accept", | ||
| 58 | "loopback pair listener", "loopback pair app end", "loopback pair pump end", | ||
| 59 | "nostr relay" | ||
| 60 | }; | ||
| 61 | |||
| 62 | static string SockAccountingText() | ||
| 63 | { | ||
| 64 | long long nOpened[SOCK_SITES], nClosed[SOCK_SITES], nFailed[SOCK_SITES], nUntagged = 0; | ||
| 65 | BtfSockSnapshot(nOpened, nClosed, nFailed, &nUntagged); | ||
| 66 | |||
| 67 | string str = "\n sockets by where they were created\n"; | ||
| 68 | str += " site opened closed live close failed\n"; | ||
| 69 | for (int i = 0; i < SOCK_SITES; i++) | ||
| 70 | { | ||
| 71 | if (nOpened[i] == 0) | ||
| 72 | continue; | ||
| 73 | str += strprintf(" %-24s %8lld %8lld %8lld %13lld\n", pszSockSite[i], | ||
| 74 | nOpened[i], nClosed[i], nOpened[i] - nClosed[i], nFailed[i]); | ||
| 75 | } | ||
| 76 | str += strprintf(" %-24s %8s %8lld %8s %13s\n", "closed but never tagged", | ||
| 77 | "-", nUntagged, "-", "-"); | ||
| 78 | return str; | ||
| 79 | } | ||
| 80 | |||
| 54 | // Defined further down, next to the counter it reads. The miner keeps its own | 81 | // Defined further down, next to the counter it reads. The miner keeps its own |
| 55 | // live count -- threads bump it on the way in and on the way out -- which is | 82 | // live count -- threads bump it on the way in and on the way out -- which is |
| 56 | // the honest number here: how many are hashing now, not how many were asked to. | 83 | // the honest number here: how many are hashing now, not how many were asked to. |
| @@ -119,6 +146,8 @@ string GetDiagnosticsText() | |||
| 119 | (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str() | 146 | (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str() |
| 120 | : ", not mining"); | 147 | : ", not mining"); |
| 121 | 148 | ||
| 149 | str += SockAccountingText(); | ||
| 150 | |||
| 122 | str += "\n peer dir height last recv last send unsent\n"; | 151 | str += "\n peer dir height last recv last send unsent\n"; |
| 123 | foreach(CNode* pnode, vCopy) | 152 | foreach(CNode* pnode, vCopy) |
| 124 | { | 153 | { |
| @@ -157,7 +186,7 @@ bool GetMyExternalIP(unsigned int& ipRet) | |||
| 157 | if (getaddrinfo(svc.host, "80", &hints, &res) != 0 || !res) | 186 | if (getaddrinfo(svc.host, "80", &hints, &res) != 0 || !res) |
| 158 | continue; | 187 | continue; |
| 159 | 188 | ||
| 160 | SOCKET hSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); | 189 | SOCKET hSocket = BtfSocketTag(socket(res->ai_family, res->ai_socktype, res->ai_protocol), SOCK_EXTIP); |
| 161 | if (hSocket == INVALID_SOCKET) { freeaddrinfo(res); continue; } | 190 | if (hSocket == INVALID_SOCKET) { freeaddrinfo(res); continue; } |
| 162 | 191 | ||
| 163 | // 5-second timeout so a dead service doesn't stall startup | 192 | // 5-second timeout so a dead service doesn't stall startup |
| @@ -173,7 +202,7 @@ bool GetMyExternalIP(unsigned int& ipRet) | |||
| 173 | 202 | ||
| 174 | if (connect(hSocket, res->ai_addr, (int)res->ai_addrlen) != 0) | 203 | if (connect(hSocket, res->ai_addr, (int)res->ai_addrlen) != 0) |
| 175 | { | 204 | { |
| 176 | freeaddrinfo(res); closesocket(hSocket); continue; | 205 | freeaddrinfo(res); BtfCloseSocket(hSocket); continue; |
| 177 | } | 206 | } |
| 178 | freeaddrinfo(res); | 207 | freeaddrinfo(res); |
| 179 | 208 | ||
| @@ -190,7 +219,7 @@ bool GetMyExternalIP(unsigned int& ipRet) | |||
| 190 | response += buf; | 219 | response += buf; |
| 191 | if (response.size() > 4096) break; | 220 | if (response.size() > 4096) break; |
| 192 | } | 221 | } |
| 193 | closesocket(hSocket); | 222 | BtfCloseSocket(hSocket); |
| 194 | 223 | ||
| 195 | // Find blank line separating headers from body | 224 | // Find blank line separating headers from body |
| 196 | size_t bodyPos = response.find("\r\n\r\n"); | 225 | size_t bodyPos = response.find("\r\n\r\n"); |
| @@ -950,7 +979,7 @@ void CNode::Disconnect() | |||
| 950 | { | 979 | { |
| 951 | LogPrint("net", "disconnecting node %s\n", addr.ToString().c_str()); | 980 | LogPrint("net", "disconnecting node %s\n", addr.ToString().c_str()); |
| 952 | 981 | ||
| 953 | closesocket(hSocket); | 982 | BtfCloseSocket(hSocket); |
| 954 | 983 | ||
| 955 | // All of a nodes broadcasts and subscriptions are automatically torn down | 984 | // All of a nodes broadcasts and subscriptions are automatically torn down |
| 956 | // when it goes down, so a node has to stay up to keep its broadcast going. | 985 | // when it goes down, so a node has to stay up to keep its broadcast going. |
| @@ -1230,7 +1259,7 @@ void ThreadSocketHandler2(void* parg) | |||
| 1230 | #else | 1259 | #else |
| 1231 | socklen_t len = sizeof(sockaddr); | 1260 | socklen_t len = sizeof(sockaddr); |
| 1232 | #endif | 1261 | #endif |
| 1233 | SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len); | 1262 | SOCKET hSocket = BtfSocketTag(accept(hListenSocket, (struct sockaddr*)&sockaddr, &len), SOCK_ACCEPT); |
| 1234 | CAddress addr(sockaddr); | 1263 | CAddress addr(sockaddr); |
| 1235 | if (hSocket == INVALID_SOCKET) | 1264 | if (hSocket == INVALID_SOCKET) |
| 1236 | { | 1265 | { |
| @@ -1251,7 +1280,7 @@ void ThreadSocketHandler2(void* parg) | |||
| 1251 | { | 1280 | { |
| 1252 | LogPrint("net", "refusing connection from %s, already at %u\n", | 1281 | LogPrint("net", "refusing connection from %s, already at %u\n", |
| 1253 | addr.ToString().c_str(), nNodes); | 1282 | addr.ToString().c_str(), nNodes); |
| 1254 | closesocket(hSocket); | 1283 | BtfCloseSocket(hSocket); |
| 1255 | } | 1284 | } |
| 1256 | else | 1285 | else |
| 1257 | { | 1286 | { |
| @@ -1639,7 +1668,7 @@ bool StartNode(string& strError) | |||
| 1639 | printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); | 1668 | printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str()); |
| 1640 | 1669 | ||
| 1641 | // Create socket for listening for incoming connections | 1670 | // Create socket for listening for incoming connections |
| 1642 | SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | 1671 | SOCKET hListenSocket = BtfSocketTag(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), SOCK_LISTEN); |
| 1643 | if (hListenSocket == INVALID_SOCKET) | 1672 | if (hListenSocket == INVALID_SOCKET) |
| 1644 | { | 1673 | { |
| 1645 | strError = strprintf("Error: Couldn't open socket for incoming connections (socket returned error %d)", WSAGetLastError()); | 1674 | strError = strprintf("Error: Couldn't open socket for incoming connections (socket returned error %d)", WSAGetLastError()); |
| @@ -121,6 +121,8 @@ extern int64 nBlocksWithoutParent; // ...of those, how many arrived orphaned | |||
| 121 | // user pastes into an issue is what a developer already knows how to read. | 121 | // user pastes into an issue is what a developer already knows how to read. |
| 122 | string GetDiagnosticsText(); | 122 | string GetDiagnosticsText(); |
| 123 | 123 | ||
| 124 | #include "sockcount.h" | ||
| 125 | |||
| 124 | // Defined in main.cpp. Declared here because CNode announces it in the version | 126 | // Defined in main.cpp. Declared here because CNode announces it in the version |
| 125 | // message, and net.h is included before main.h. | 127 | // message, and net.h is included before main.h. |
| 126 | extern int nBestHeight; | 128 | extern int nBestHeight; |
diff --git a/src/nostr.cpp b/src/nostr.cpp index 17139ad..dc96be2 100644 --- a/src/nostr.cpp +++ b/src/nostr.cpp | |||
| @@ -462,7 +462,7 @@ public: | |||
| 462 | { | 462 | { |
| 463 | if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); ssl = NULL; } | 463 | if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); ssl = NULL; } |
| 464 | if (sslctx) { SSL_CTX_free(sslctx); sslctx = NULL; } | 464 | if (sslctx) { SSL_CTX_free(sslctx); sslctx = NULL; } |
| 465 | if (hSocket != INVALID_SOCKET) { closesocket(hSocket); hSocket = INVALID_SOCKET; } | 465 | if (hSocket != INVALID_SOCKET) { BtfCloseSocket(hSocket); hSocket = INVALID_SOCKET; } |
| 466 | } | 466 | } |
| 467 | 467 | ||
| 468 | int RawRead(char* buf, int len) | 468 | int RawRead(char* buf, int len) |
| @@ -518,7 +518,7 @@ public: | |||
| 518 | bool fConnected = false; | 518 | bool fConnected = false; |
| 519 | for (rp = res; rp != NULL; rp = rp->ai_next) | 519 | for (rp = res; rp != NULL; rp = rp->ai_next) |
| 520 | { | 520 | { |
| 521 | hSocket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); | 521 | hSocket = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), SOCK_NOSTR); |
| 522 | if (hSocket == INVALID_SOCKET) | 522 | if (hSocket == INVALID_SOCKET) |
| 523 | continue; | 523 | continue; |
| 524 | 524 | ||
| @@ -542,7 +542,7 @@ public: | |||
| 542 | break; | 542 | break; |
| 543 | } | 543 | } |
| 544 | 544 | ||
| 545 | closesocket(hSocket); | 545 | BtfCloseSocket(hSocket); |
| 546 | hSocket = INVALID_SOCKET; | 546 | hSocket = INVALID_SOCKET; |
| 547 | } | 547 | } |
| 548 | freeaddrinfo(res); | 548 | freeaddrinfo(res); |
diff --git a/src/rpc.cpp b/src/rpc.cpp index 97d8429..41c0328 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -46,10 +46,10 @@ | |||
| 46 | using json = nlohmann::json; | 46 | using json = nlohmann::json; |
| 47 | 47 | ||
| 48 | #ifdef _WIN32 | 48 | #ifdef _WIN32 |
| 49 | #define sock_close(s) closesocket(s) | 49 | #define sock_close(s) BtfCloseSocket(s) |
| 50 | #define SEND_FLAGS 0 | 50 | #define SEND_FLAGS 0 |
| 51 | #else | 51 | #else |
| 52 | #define sock_close(s) ::close(s) | 52 | #define sock_close(s) BtfCloseSocket(s) |
| 53 | #define SEND_FLAGS MSG_NOSIGNAL | 53 | #define SEND_FLAGS MSG_NOSIGNAL |
| 54 | #endif | 54 | #endif |
| 55 | 55 | ||
diff --git a/src/sockcount.h b/src/sockcount.h new file mode 100644 index 0000000..19f0827 --- /dev/null +++ b/src/sockcount.h | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | // | ||
| 4 | // Socket accounting -- which part of the program is holding the sockets. | ||
| 5 | // | ||
| 6 | // A node on a 32-core machine was found with 1502 sockets in Windows' "bound" | ||
| 7 | // state: created, never connected, never closed, each holding a distinct | ||
| 8 | // ephemeral port. That is 9% of the machine's whole dynamic port range gone in | ||
| 9 | // nineteen hours, and when the range runs out nothing on that machine can open | ||
| 10 | // an outbound connection -- not just this program. | ||
| 11 | // | ||
| 12 | // Reading the code did not find it. Every connect() failure path in this tree | ||
| 13 | // closes its socket. So count instead: tag each socket where it is created, | ||
| 14 | // untag it where it is closed, and report the difference per site. Whichever | ||
| 15 | // site's live count tracks the leak is the answer. | ||
| 16 | // | ||
| 17 | // Header-only on purpose. btfrv.cpp is compiled a second time, on its own, into | ||
| 18 | // the standalone rendezvous relay -- a separate tree that does not link net.cpp. | ||
| 19 | // Anything here that needed a .cpp would break that build, and it would break | ||
| 20 | // it on the VPS rather than on this machine. Include after the platform socket | ||
| 21 | // headers; each of those files defines SOCKET for itself. | ||
| 22 | |||
| 23 | #ifndef BITFLASH_SOCKCOUNT_H | ||
| 24 | #define BITFLASH_SOCKCOUNT_H | ||
| 25 | |||
| 26 | #include <map> | ||
| 27 | #include <mutex> | ||
| 28 | |||
| 29 | #ifndef _WIN32 | ||
| 30 | #include <unistd.h> | ||
| 31 | #endif | ||
| 32 | |||
| 33 | enum | ||
| 34 | { | ||
| 35 | SOCK_EXTIP, // GetMyExternalIP probe | ||
| 36 | SOCK_LISTEN, // the node's own listening socket | ||
| 37 | SOCK_ACCEPT, // inbound connections | ||
| 38 | SOCK_RV_DIAL, // outbound to a rendezvous relay | ||
| 39 | SOCK_RV_LISTEN, // rendezvous listener | ||
| 40 | SOCK_RV_ACCEPT, // inbound at the rendezvous | ||
| 41 | SOCK_PAIR_LISTEN, // loopback pair: the temporary listener | ||
| 42 | SOCK_PAIR_APP, // loopback pair: the app end handed to CNode | ||
| 43 | SOCK_PAIR_PUMP, // loopback pair: the pump end | ||
| 44 | SOCK_NOSTR, // outbound to a Nostr relay | ||
| 45 | SOCK_SITES | ||
| 46 | }; | ||
| 47 | |||
| 48 | struct BtfSockAccount | ||
| 49 | { | ||
| 50 | std::mutex mtx; | ||
| 51 | std::map<SOCKET, int> mapSite; | ||
| 52 | long long nOpened[SOCK_SITES]; | ||
| 53 | long long nClosed[SOCK_SITES]; | ||
| 54 | long long nCloseFailed[SOCK_SITES]; | ||
| 55 | long long nClosedUntagged; | ||
| 56 | BtfSockAccount() : nClosedUntagged(0) | ||
| 57 | { | ||
| 58 | for (int i = 0; i < SOCK_SITES; i++) { nOpened[i] = 0; nClosed[i] = 0; nCloseFailed[i] = 0; } | ||
| 59 | } | ||
| 60 | }; | ||
| 61 | |||
| 62 | // One instance across every translation unit: a function-local static in an | ||
| 63 | // inline function is the same object everywhere it is used. | ||
| 64 | inline BtfSockAccount& BtfSockAccounting() | ||
| 65 | { | ||
| 66 | static BtfSockAccount account; | ||
| 67 | return account; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Record a freshly created socket and return it unchanged, so call sites read | ||
| 71 | // as they did before. INVALID_SOCKET passes through untouched. | ||
| 72 | inline SOCKET BtfSocketTag(SOCKET hSocket, int nSite) | ||
| 73 | { | ||
| 74 | if (hSocket == INVALID_SOCKET || nSite < 0 || nSite >= SOCK_SITES) | ||
| 75 | return hSocket; | ||
| 76 | BtfSockAccount& a = BtfSockAccounting(); | ||
| 77 | std::lock_guard<std::mutex> lock(a.mtx); | ||
| 78 | a.mapSite[hSocket] = nSite; | ||
| 79 | a.nOpened[nSite]++; | ||
| 80 | return hSocket; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Close a socket and forget it. Closing one nobody tagged is counted too -- | ||
| 84 | // that number staying at zero is what says the accounting is complete. | ||
| 85 | inline int BtfCloseSocket(SOCKET hSocket) | ||
| 86 | { | ||
| 87 | if (hSocket == INVALID_SOCKET) | ||
| 88 | return 0; | ||
| 89 | int nSite = -1; | ||
| 90 | { | ||
| 91 | BtfSockAccount& a = BtfSockAccounting(); | ||
| 92 | std::lock_guard<std::mutex> lock(a.mtx); | ||
| 93 | std::map<SOCKET, int>::iterator mi = a.mapSite.find(hSocket); | ||
| 94 | if (mi == a.mapSite.end()) | ||
| 95 | a.nClosedUntagged++; | ||
| 96 | else | ||
| 97 | { | ||
| 98 | nSite = mi->second; | ||
| 99 | a.nClosed[nSite]++; | ||
| 100 | a.mapSite.erase(mi); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | #ifdef _WIN32 | ||
| 104 | int nRet = ::closesocket(hSocket); | ||
| 105 | #else | ||
| 106 | int nRet = ::close(hSocket); | ||
| 107 | #endif | ||
| 108 | // A close that fails leaves the descriptor -- and on Windows the ephemeral | ||
| 109 | // port it holds -- in place. Counted because a socket the program believes | ||
| 110 | // it released and the operating system still shows is exactly the shape of | ||
| 111 | // the leak being chased, and nothing in this codebase has ever looked at | ||
| 112 | // what closesocket returns. | ||
| 113 | if (nRet != 0 && nSite >= 0) | ||
| 114 | { | ||
| 115 | BtfSockAccount& a = BtfSockAccounting(); | ||
| 116 | std::lock_guard<std::mutex> lock(a.mtx); | ||
| 117 | a.nCloseFailed[nSite]++; | ||
| 118 | } | ||
| 119 | return nRet; | ||
| 120 | } | ||
| 121 | |||
| 122 | // Copy the counters out. Formatting lives in net.cpp, which has strprintf -- | ||
| 123 | // and which the relay build does not compile. | ||
| 124 | inline void BtfSockSnapshot(long long* pOpened, long long* pClosed, long long* pFailed, long long* pUntagged) | ||
| 125 | { | ||
| 126 | BtfSockAccount& a = BtfSockAccounting(); | ||
| 127 | std::lock_guard<std::mutex> lock(a.mtx); | ||
| 128 | for (int i = 0; i < SOCK_SITES; i++) | ||
| 129 | { | ||
| 130 | pOpened[i] = a.nOpened[i]; | ||
| 131 | pClosed[i] = a.nClosed[i]; | ||
| 132 | pFailed[i] = a.nCloseFailed[i]; | ||
| 133 | } | ||
| 134 | *pUntagged = a.nClosedUntagged; | ||
| 135 | } | ||
| 136 | |||
| 137 | #endif | ||