Reclaim outbound slots on peer disconnect: - TcpClient_ThreadProc fired on_disconnect but never cleared the outbound slot, closed the fd, joined the io thread, or freed the connection, so a dead peer permanently held its outboundClients[] slot. After MAX_CONS (32) churned connections the node could make no new outbound connections, and leaked fds/threads/memory. (The inbound side already self-reclaimed.) - Add a reaper (Node_ReapDeadOutbound) on the maintenance thread: under outboundLock it detaches dead (disconnect-notified) slots, then joins the io thread and destroys/frees each connection outside the lock. - Guard against use-after-free with a pin count on tcp_connection_t (TcpConnection_Pin/Unpin). The only cross-thread consumer holding a raw connection pointer across a blocking op is the `sync` command (via Node_GetBestOutboundPeer); it now pins the peer and unpins when done, and the reaper skips pinned connections. Discovery's snapshots run on the reaper's own thread, so they need no pin. - Node_GetBestOutboundPeer/GetClientList/GetPeerEndpoints skip disconnect-notified connections so a dead peer is never handed out. - Node_Destroy stops+joins the maintenance thread before tearing down outbound clients, so the reaper can't race shutdown. Strike disconnected peers from the discovery peer list: - Add NodeDiscovery_RemovePeer + Node_HandlePeerDisconnect: on disconnect, remove the peer from the known-peer table once no live connection (inbound or outbound) to its listen endpoint remains (Node_HasLiveConnectionTo; disconnect-notified conns don't count, so both directions dropping at once is handled). Wired into Node_Server_OnDisconnect and Node_Client_OnDisconnect.
335 lines
8.8 KiB
C
335 lines
8.8 KiB
C
#ifndef _WIN32
|
|
|
|
#include <tcpd/tcpconnection.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_storage* peerAddr, tcp_connection_role_t role) {
|
|
if (!conn || sockFd < 0 || !peerAddr) {
|
|
return -1;
|
|
}
|
|
|
|
memset(conn, 0, sizeof(*conn));
|
|
conn->sockFd = sockFd;
|
|
conn->peerAddr = *peerAddr;
|
|
conn->addrFamily = peerAddr->ss_family;
|
|
conn->role = role;
|
|
|
|
if (pthread_mutex_init(&conn->sendLock, NULL) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (pthread_mutex_init(&conn->stateLock, NULL) != 0) {
|
|
pthread_mutex_destroy(&conn->sendLock);
|
|
return -1;
|
|
}
|
|
|
|
conn->closing = false;
|
|
conn->disconnectedNotified = false;
|
|
atomic_init(&conn->pinCount, 0);
|
|
conn->dataBuf = NULL;
|
|
conn->dataBufLen = 0;
|
|
conn->dataBufCap = 0;
|
|
|
|
TcpConnection_ResetFramingState(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TcpConnection_Destroy(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
|
|
if (conn->sockFd >= 0) {
|
|
close(conn->sockFd);
|
|
conn->sockFd = -1;
|
|
}
|
|
|
|
free(conn->dataBuf);
|
|
conn->dataBuf = NULL;
|
|
conn->dataBufLen = 0;
|
|
conn->dataBufCap = 0;
|
|
|
|
free(conn->frameBuf);
|
|
conn->frameBuf = NULL;
|
|
conn->frameBytesRead = 0;
|
|
|
|
pthread_mutex_destroy(&conn->stateLock);
|
|
pthread_mutex_destroy(&conn->sendLock);
|
|
}
|
|
|
|
int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* data, size_t len) {
|
|
if (!conn || (!data && len > 0)) {
|
|
return -1;
|
|
}
|
|
|
|
if (len > conn->dataBufCap) {
|
|
unsigned char* resized = (unsigned char*)realloc(conn->dataBuf, len);
|
|
if (!resized) {
|
|
return -1;
|
|
}
|
|
|
|
conn->dataBuf = resized;
|
|
conn->dataBufCap = len;
|
|
}
|
|
|
|
if (len > 0) {
|
|
memcpy(conn->dataBuf, data, len);
|
|
}
|
|
conn->dataBufLen = len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TcpConnection_ResetFramingState(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
|
|
memset(conn->headerBuf, 0, sizeof(conn->headerBuf));
|
|
conn->headerBytesRead = 0;
|
|
conn->expectedPayloadLen = 0;
|
|
conn->frameBytesRead = 0;
|
|
|
|
free(conn->frameBuf);
|
|
conn->frameBuf = NULL;
|
|
}
|
|
|
|
int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen) {
|
|
if (!conn || (!input && inputLen > 0)) {
|
|
return -1;
|
|
}
|
|
|
|
size_t offset = 0;
|
|
|
|
while (offset < inputLen) {
|
|
if (conn->headerBytesRead < TCP_FRAME_HEADER_SIZE) {
|
|
size_t needed = TCP_FRAME_HEADER_SIZE - conn->headerBytesRead;
|
|
size_t take = (inputLen - offset < needed) ? (inputLen - offset) : needed;
|
|
|
|
memcpy(conn->headerBuf + conn->headerBytesRead, input + offset, take);
|
|
conn->headerBytesRead += take;
|
|
offset += take;
|
|
|
|
if (conn->headerBytesRead < TCP_FRAME_HEADER_SIZE) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t beLen = 0;
|
|
memcpy(&beLen, conn->headerBuf, sizeof(beLen));
|
|
conn->expectedPayloadLen = ntohl(beLen);
|
|
|
|
if (conn->expectedPayloadLen > TCP_MAX_FRAME_PAYLOAD) {
|
|
TcpConnection_ResetFramingState(conn);
|
|
return -1;
|
|
}
|
|
|
|
if (conn->expectedPayloadLen == 0) {
|
|
if (TcpConnection_SetDataBuffer(conn, NULL, 0) != 0) {
|
|
TcpConnection_ResetFramingState(conn);
|
|
return -1;
|
|
}
|
|
|
|
if (conn->on_data) {
|
|
conn->on_data(conn);
|
|
}
|
|
|
|
conn->headerBytesRead = 0;
|
|
conn->expectedPayloadLen = 0;
|
|
continue;
|
|
}
|
|
|
|
conn->frameBuf = (unsigned char*)malloc(conn->expectedPayloadLen);
|
|
if (!conn->frameBuf) {
|
|
TcpConnection_ResetFramingState(conn);
|
|
return -1;
|
|
}
|
|
conn->frameBytesRead = 0;
|
|
}
|
|
|
|
size_t frameRemaining = conn->expectedPayloadLen - conn->frameBytesRead;
|
|
size_t take = (inputLen - offset < frameRemaining) ? (inputLen - offset) : frameRemaining;
|
|
|
|
memcpy(conn->frameBuf + conn->frameBytesRead, input + offset, take);
|
|
conn->frameBytesRead += take;
|
|
offset += take;
|
|
|
|
if (conn->frameBytesRead == conn->expectedPayloadLen) {
|
|
if (TcpConnection_SetDataBuffer(conn, conn->frameBuf, conn->expectedPayloadLen) != 0) {
|
|
TcpConnection_ResetFramingState(conn);
|
|
return -1;
|
|
}
|
|
|
|
if (conn->on_data) {
|
|
conn->on_data(conn);
|
|
}
|
|
|
|
conn->headerBytesRead = 0;
|
|
conn->expectedPayloadLen = 0;
|
|
conn->frameBytesRead = 0;
|
|
free(conn->frameBuf);
|
|
conn->frameBuf = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TcpConnection_SendRaw(int sockFd, const void* data, size_t len) {
|
|
if (sockFd < 0 || (!data && len > 0)) {
|
|
return -1;
|
|
}
|
|
|
|
size_t totalSent = 0;
|
|
const unsigned char* ptr = (const unsigned char*)data;
|
|
|
|
while (totalSent < len) {
|
|
ssize_t sent = send(sockFd, ptr + totalSent, len - totalSent, 0);
|
|
if (sent < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
return -1;
|
|
}
|
|
if (sent == 0) {
|
|
return -1;
|
|
}
|
|
|
|
totalSent += (size_t)sent;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen) {
|
|
if (!conn || (!payload && payloadLen > 0) || payloadLen > TCP_MAX_FRAME_PAYLOAD) {
|
|
return -1;
|
|
}
|
|
|
|
uint32_t beLen = htonl((uint32_t)payloadLen);
|
|
|
|
pthread_mutex_lock(&conn->sendLock);
|
|
|
|
int rc = TcpConnection_SendRaw(conn->sockFd, &beLen, sizeof(beLen));
|
|
if (rc == 0 && payloadLen > 0) {
|
|
rc = TcpConnection_SendRaw(conn->sockFd, payload, payloadLen);
|
|
}
|
|
|
|
pthread_mutex_unlock(&conn->sendLock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void TcpConnection_RequestClose(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
|
|
pthread_mutex_lock(&conn->stateLock);
|
|
if (!conn->closing) {
|
|
conn->closing = true;
|
|
if (conn->sockFd >= 0) {
|
|
shutdown(conn->sockFd, SHUT_RDWR);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&conn->stateLock);
|
|
}
|
|
|
|
void TcpConnection_MarkDisconnectNotified(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
|
|
pthread_mutex_lock(&conn->stateLock);
|
|
conn->disconnectedNotified = true;
|
|
pthread_mutex_unlock(&conn->stateLock);
|
|
}
|
|
|
|
bool TcpConnection_IsDisconnectNotified(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return true;
|
|
}
|
|
|
|
pthread_mutex_lock(&conn->stateLock);
|
|
bool notified = conn->disconnectedNotified;
|
|
pthread_mutex_unlock(&conn->stateLock);
|
|
|
|
return notified;
|
|
}
|
|
|
|
void TcpConnection_Pin(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
atomic_fetch_add(&conn->pinCount, 1);
|
|
}
|
|
|
|
void TcpConnection_Unpin(tcp_connection_t* conn) {
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
atomic_fetch_sub(&conn->pinCount, 1);
|
|
}
|
|
|
|
static int extract_v4(const tcp_connection_t* conn, struct in_addr* v4out) {
|
|
if (conn->addrFamily == AF_INET6) {
|
|
const struct sockaddr_in6* a6 = (const struct sockaddr_in6*)&conn->peerAddr;
|
|
if (IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
|
|
memcpy(v4out, &a6->sin6_addr.s6_addr[12], sizeof(*v4out));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
*v4out = ((const struct sockaddr_in*)&conn->peerAddr)->sin_addr;
|
|
return 1;
|
|
}
|
|
|
|
const char* TcpConnection_GetPeerAddrStr(const tcp_connection_t* conn, char* buf, size_t bufLen) {
|
|
if (!conn || !buf || bufLen == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (conn->addrFamily == AF_INET6) {
|
|
const struct sockaddr_in6* a6 = (const struct sockaddr_in6*)&conn->peerAddr;
|
|
if (IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
|
|
struct in_addr v4;
|
|
memcpy(&v4, &a6->sin6_addr.s6_addr[12], sizeof(v4));
|
|
return inet_ntop(AF_INET, &v4, buf, (socklen_t)bufLen);
|
|
}
|
|
return inet_ntop(AF_INET6, &a6->sin6_addr, buf, (socklen_t)bufLen);
|
|
}
|
|
|
|
const struct sockaddr_in* a4 = (const struct sockaddr_in*)&conn->peerAddr;
|
|
return inet_ntop(AF_INET, &a4->sin_addr, buf, (socklen_t)bufLen);
|
|
}
|
|
|
|
int TcpConnection_PeerAddrEqual(const tcp_connection_t* a, const tcp_connection_t* b) {
|
|
if (!a || !b) {
|
|
return 0;
|
|
}
|
|
|
|
struct in_addr va, vb;
|
|
int a_is_v4 = extract_v4(a, &va);
|
|
int b_is_v4 = extract_v4(b, &vb);
|
|
|
|
if (a_is_v4 && b_is_v4) {
|
|
return va.s_addr == vb.s_addr;
|
|
}
|
|
|
|
if (!a_is_v4 && !b_is_v4) {
|
|
const struct in6_addr* aa6 = &((const struct sockaddr_in6*)&a->peerAddr)->sin6_addr;
|
|
const struct in6_addr* ab6 = &((const struct sockaddr_in6*)&b->peerAddr)->sin6_addr;
|
|
return memcmp(aa6, ab6, sizeof(*aa6)) == 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|