diff --git a/include/tcpd/tcpconnection.h b/include/tcpd/tcpconnection.h index 51f62b5..98ade67 100644 --- a/include/tcpd/tcpconnection.h +++ b/include/tcpd/tcpconnection.h @@ -6,6 +6,7 @@ #include #include #include +#include #define TCP_IO_BUFFER_SIZE 1500 #define TCP_FRAME_HEADER_SIZE 4U @@ -19,17 +20,9 @@ typedef enum { typedef struct tcp_connection_t tcp_connection_t; struct tcp_connection_t { - // TODO: We should make it so only ONE of this needs to be available. - // Because of my temporary "I just need something that works" horseshit that I'm about to write, you'll need IPv4 and IPv6 is optional. - // Note to self: Don't pull an IETF and some "NAT exists, we're fine" bullshit, because if we end up with our eqvivalent of Teredo or CGNAT, I'm gonna be fucking pissed. - // And no, the solution isn't "eh, just bind to 0.0.0.0 and ignore it", because if we do that, we'll inevitably end up with a host that only has IPv6 and then we'll be fucked. - // Honestly, I'm proud of whoever runs IPv6-only. Brave soul. int sockFd; - struct sockaddr_in peerAddr; -#ifdef USE_IPV6 - int sockFd6; // For IPv6 support - struct sockaddr_in6 peerAddr6; // For IPv6 support -#endif + sa_family_t addrFamily; + struct sockaddr_storage peerAddr; uint32_t connectionId; tcp_connection_role_t role; @@ -55,7 +48,7 @@ struct tcp_connection_t { void* owner; }; -int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_in* peerAddr, tcp_connection_role_t role); +int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_storage* peerAddr, tcp_connection_role_t role); void TcpConnection_Destroy(tcp_connection_t* conn); int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* data, size_t len); @@ -63,7 +56,14 @@ int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* dat void TcpConnection_ResetFramingState(tcp_connection_t* conn); int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen); -// This just takes a socket ID, so it's independent from the v4/v6 stuff. It works for both. +// Returns the peer's canonical IP string (strips ::ffff: IPv4-mapped prefix). +// Writes at most bufLen bytes to buf. Returns buf on success, NULL on failure. +const char* TcpConnection_GetPeerAddrStr(const tcp_connection_t* conn, char* buf, size_t bufLen); + +// Returns non-zero if both connections have the same peer IP address. +// Handles AF_INET vs AF_INET6 mismatches via IPv4-mapped normalisation. +int TcpConnection_PeerAddrEqual(const tcp_connection_t* a, const tcp_connection_t* b); + int TcpConnection_SendRaw(int sockFd, const void* data, size_t len); int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen); diff --git a/include/tcpd/tcpserver.h b/include/tcpd/tcpserver.h index b772f35..2d7fa40 100644 --- a/include/tcpd/tcpserver.h +++ b/include/tcpd/tcpserver.h @@ -9,14 +9,9 @@ #include typedef struct { - int sockFd; - struct sockaddr_in addr; -#ifdef USE_IPV6 - int sockFd6; // IPv6 support - struct sockaddr_in6 addr6; // IPv6 support -#endif + int sockFd; // IPv6 listening socket (-1 if IPv6 unavailable) + int sockFdV4; // IPv4 listening socket (-1 on bind failure) int opt; - int opt6; // IPv6 support int isRunning; void* owner; @@ -32,7 +27,8 @@ typedef struct { tcp_connection_t** clientsArrPtr; pthread_mutex_t clientsMutex; - pthread_t svrThread; + pthread_t svrThread; // IPv6 accept thread + pthread_t svrThreadV4; // IPv4 accept thread } tcp_server_t; struct tcpclient_thread_args { diff --git a/src/nets/net_node.c b/src/nets/net_node.c index 6c8c586..bad88cb 100644 --- a/src/nets/net_node.c +++ b/src/nets/net_node.c @@ -255,7 +255,7 @@ net_node_t* Node_Create() { node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes TxMempool_Init(); - TcpServer_Init(node->server, listenPort, "0.0.0.0"); + TcpServer_Init(node->server, listenPort, "::"); node->server->owner = node; node->server->on_connect = Node_Server_OnConnect; @@ -436,8 +436,8 @@ void Node_Server_OnConnect(tcp_connection_t* client) { if (echoPeersEnabled && node && client) { // Attempt to create an outbound connection back to the peer's IP on our configured port. // We avoid connecting if we already have an outbound to the same IP. - char ipbuf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &client->peerAddr.sin_addr, ipbuf, sizeof(ipbuf))) { + char ipbuf[INET6_ADDRSTRLEN]; + if (TcpConnection_GetPeerAddrStr(client, ipbuf, sizeof(ipbuf))) { // Use the configured port as the target port for the peer's listening service. unsigned short targetPort = listenPort; @@ -445,8 +445,7 @@ void Node_Server_OnConnect(tcp_connection_t* client) { pthread_mutex_lock(&node->outboundLock); for (size_t i = 0; i < MAX_CONS; ++i) { if (node->outboundClients[i].connection) { - struct in_addr otherAddr = node->outboundClients[i].connection->peerAddr.sin_addr; - if (otherAddr.s_addr == client->peerAddr.sin_addr.s_addr) { + if (TcpConnection_PeerAddrEqual(node->outboundClients[i].connection, client)) { shouldConnect = 0; break; } @@ -930,11 +929,6 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp size_t chainSize = Chain_Size(currentChain); if (startHeightInclusive >= chainSize) return; - uint32_t sourceIp = 0; - if (sourceConn) { - sourceIp = sourceConn->peerAddr.sin_addr.s_addr; - } - for (size_t h = startHeightInclusive; h < chainSize; ++h) { block_t* blk = NULL; if (!Chain_GetBlockCopy(currentChain, h, &blk) || !blk) { @@ -997,7 +991,7 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp tcp_connection_t* conn = node->outboundClients[i].connection; if (!conn) continue; if (conn == sourceConn) continue; - if (sourceIp != 0 && conn->peerAddr.sin_addr.s_addr == sourceIp) continue; + if (sourceConn && TcpConnection_PeerAddrEqual(conn, sourceConn)) continue; Node_SendPacket(node, conn, PACKET_TYPE_BROADCAST_BLOCK, payload, off); } pthread_mutex_unlock(&node->outboundLock); diff --git a/src/tcpd/tcpclient.c b/src/tcpd/tcpclient.c index 23c6ae0..3437aee 100644 --- a/src/tcpd/tcpclient.c +++ b/src/tcpd/tcpclient.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -82,18 +83,34 @@ int TcpClient_Connect( return -1; } - int sockFd = socket(AF_INET, SOCK_STREAM, 0); - if (sockFd < 0) { + // Detect address family from the IP string + struct sockaddr_in6 addr6; + struct sockaddr_in addr4; + struct sockaddr* pSockAddr; + socklen_t sockAddrLen; + int af; + + memset(&addr6, 0, sizeof(addr6)); + memset(&addr4, 0, sizeof(addr4)); + + if (inet_pton(AF_INET6, peerIp, &addr6.sin6_addr) == 1) { + af = AF_INET6; + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(peerPort); + pSockAddr = (struct sockaddr*)&addr6; + sockAddrLen = sizeof(addr6); + } else if (inet_pton(AF_INET, peerIp, &addr4.sin_addr) == 1) { + af = AF_INET; + addr4.sin_family = AF_INET; + addr4.sin_port = htons(peerPort); + pSockAddr = (struct sockaddr*)&addr4; + sockAddrLen = sizeof(addr4); + } else { return -1; } - struct sockaddr_in peerAddr; - memset(&peerAddr, 0, sizeof(peerAddr)); - peerAddr.sin_family = AF_INET; - peerAddr.sin_port = htons(peerPort); - - if (inet_pton(AF_INET, peerIp, &peerAddr.sin_addr) <= 0) { - close(sockFd); + int sockFd = socket(af, SOCK_STREAM, 0); + if (sockFd < 0) { return -1; } @@ -102,7 +119,7 @@ int TcpClient_Connect( if (flags == -1) flags = 0; fcntl(sockFd, F_SETFL, flags | O_NONBLOCK); - int rc = connect(sockFd, (struct sockaddr*)&peerAddr, sizeof(peerAddr)); + int rc = connect(sockFd, pSockAddr, sockAddrLen); if (rc < 0) { if (errno != EINPROGRESS) { close(sockFd); @@ -143,13 +160,18 @@ int TcpClient_Connect( // Restore blocking mode fcntl(sockFd, F_SETFL, flags & ~O_NONBLOCK); + // Pack the address into sockaddr_storage for TcpConnection_Init + struct sockaddr_storage peerStorage; + memset(&peerStorage, 0, sizeof(peerStorage)); + memcpy(&peerStorage, pSockAddr, sockAddrLen); + tcp_connection_t* conn = (tcp_connection_t*)malloc(sizeof(*conn)); if (!conn) { close(sockFd); return -1; } - if (TcpConnection_Init(conn, sockFd, &peerAddr, TCP_CONNECTION_ROLE_OUTBOUND) != 0) { + if (TcpConnection_Init(conn, sockFd, &peerStorage, TCP_CONNECTION_ROLE_OUTBOUND) != 0) { free(conn); close(sockFd); return -1; diff --git a/src/tcpd/tcpconnection.c b/src/tcpd/tcpconnection.c index 1f8a36d..2ce2f75 100644 --- a/src/tcpd/tcpconnection.c +++ b/src/tcpd/tcpconnection.c @@ -9,7 +9,7 @@ #include #include -int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_in* peerAddr, tcp_connection_role_t role) { +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; } @@ -17,6 +17,7 @@ int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr 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) { @@ -214,26 +215,10 @@ int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t pthread_mutex_lock(&conn->sendLock); -#ifdef USE_IPV6 - int sock; - if (conn->sockFd6 >= 0) { - // IPv6 is available, attempt to send on it. If it fails, we'll fall back to IPv4 if available. - sock = conn->sockFd6; - } else { - // IPv4 fallback - sock = conn->sockFd; - } - - int rc = TcpConnection_SendRaw(sock, &beLen, sizeof(beLen)); - if (rc == 0 && payloadLen > 0) { - rc = TcpConnection_SendRaw(sock, payload, payloadLen); - } -#else int rc = TcpConnection_SendRaw(conn->sockFd, &beLen, sizeof(beLen)); if (rc == 0 && payloadLen > 0) { rc = TcpConnection_SendRaw(conn->sockFd, payload, payloadLen); } -#endif pthread_mutex_unlock(&conn->sendLock); @@ -251,11 +236,6 @@ void TcpConnection_RequestClose(tcp_connection_t* conn) { if (conn->sockFd >= 0) { shutdown(conn->sockFd, SHUT_RDWR); } -#ifdef USE_IPV6 - if (conn->sockFd6 >= 0) { - shutdown(conn->sockFd6, SHUT_RDWR); - } -#endif } pthread_mutex_unlock(&conn->stateLock); } @@ -282,4 +262,58 @@ bool TcpConnection_IsDisconnectNotified(tcp_connection_t* conn) { return notified; } +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 diff --git a/src/tcpd/tcpserver.c b/src/tcpd/tcpserver.c index 8d1f882..e68d4eb 100644 --- a/src/tcpd/tcpserver.c +++ b/src/tcpd/tcpserver.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -10,6 +11,11 @@ #include #include +typedef struct { + tcp_server_t* serverPtr; + int listenFd; +} tcpaccept_thread_args_t; + static void TcpServer_RemoveClientByPtrUnlocked(tcp_server_t* svr, tcp_connection_t* cli) { if (!svr || !svr->clientsArrPtr || !cli) { return; @@ -70,15 +76,20 @@ static void* TcpServer_clientthreadprocess(void* ptr) { } static void* TcpServer_threadprocess(void* ptr) { - tcp_server_t* svr = (tcp_server_t*)ptr; - if (!svr) { + tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)ptr; + if (!args || !args->serverPtr) { + free(args); return NULL; } + tcp_server_t* svr = args->serverPtr; + int listenFd = args->listenFd; + free(args); + while (svr->isRunning) { - struct sockaddr_in clientAddr; + struct sockaddr_storage clientAddr; socklen_t clientSize = sizeof(clientAddr); - int clientFd = accept(svr->sockFd, (struct sockaddr*)&clientAddr, &clientSize); + int clientFd = accept(listenFd, (struct sockaddr*)&clientAddr, &clientSize); if (clientFd < 0) { if (!svr->isRunning) { @@ -168,13 +179,12 @@ tcp_server_t* TcpServer_Create() { memset(svr, 0, sizeof(*svr)); svr->sockFd = -1; + svr->sockFdV4 = -1; svr->svrThread = 0; + svr->svrThreadV4 = 0; svr->isRunning = 0; svr->maxClients = 0; svr->clientsArrPtr = NULL; -#ifdef USE_IPV6 - svr->sockFd6 = -1; -#endif if (pthread_mutex_init(&svr->clientsMutex, NULL) != 0) { free(svr); @@ -203,65 +213,68 @@ void TcpServer_Init(tcp_server_t* ptr, unsigned short port, const char* addr) { return; } - ptr->sockFd = socket(AF_INET, SOCK_STREAM, 0); - if (ptr->sockFd < 0) { + ptr->opt = 1; + + // IPv6 (pure, not dual-stack — a dedicated IPv4 socket handles IPv4 clients) + int fd6 = socket(AF_INET6, SOCK_STREAM, 0); + if (fd6 >= 0) { + setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(ptr->opt)); + int v6only = 1; + setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)); + + struct sockaddr_in6 a6; + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons(port); + a6.sin6_addr = in6addr_any; + + if (bind(fd6, (struct sockaddr*)&a6, sizeof(a6)) == 0) { + ptr->sockFd = fd6; + } else { + close(fd6); + } + } + + // IPv4 (always attempted regardless of IPv6 result) + int fd4 = socket(AF_INET, SOCK_STREAM, 0); + if (fd4 >= 0) { + setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(ptr->opt)); + + struct sockaddr_in a4; + memset(&a4, 0, sizeof(a4)); + a4.sin_family = AF_INET; + a4.sin_port = htons(port); + if (inet_pton(AF_INET, addr, &a4.sin_addr) <= 0) { + a4.sin_addr.s_addr = INADDR_ANY; + } + + if (bind(fd4, (struct sockaddr*)&a4, sizeof(a4)) == 0) { + ptr->sockFdV4 = fd4; + } else { + close(fd4); + } + } +} + +void TcpServer_Start(tcp_server_t* ptr, int maxcons) { + if (!ptr || (ptr->sockFd < 0 && ptr->sockFdV4 < 0) || maxcons <= 0 || ptr->isRunning) { return; } - ptr->opt = 1; - setsockopt(ptr->sockFd, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(int)); - - memset(&ptr->addr, 0, sizeof(ptr->addr)); - ptr->addr.sin_family = AF_INET; - ptr->addr.sin_port = htons(port); - inet_pton(AF_INET, addr, &ptr->addr.sin_addr); - - if (bind(ptr->sockFd, (struct sockaddr*)&ptr->addr, sizeof(ptr->addr)) < 0) { + if (ptr->sockFd >= 0 && listen(ptr->sockFd, maxcons) < 0) { close(ptr->sockFd); ptr->sockFd = -1; } -#ifdef USE_IPV6 - // IPv6 support - ptr->sockFd6 = socket(AF_INET6, SOCK_STREAM, 0); - if (ptr->sockFd6 >= 0) { - ptr->opt6 = 1; - setsockopt(ptr->sockFd6, SOL_SOCKET, SO_REUSEADDR, &ptr->opt6, sizeof(int)); - memset(&ptr->addr6, 0, sizeof(ptr->addr6)); - ptr->addr6.sin6_family = AF_INET6; - ptr->addr6.sin6_port = htons(port); - inet_pton(AF_INET6, addr, &ptr->addr6.sin6_addr); - if (bind(ptr->sockFd6, (struct sockaddr*)&ptr->addr6, sizeof(ptr->addr6)) < 0) { - close(ptr->sockFd6); - ptr->sockFd6 = -1; - } - } else { - ptr->sockFd6 = -1; // IPv6 is optional, so if it isn't available, we just set it to -1 + if (ptr->sockFdV4 >= 0 && listen(ptr->sockFdV4, maxcons) < 0) { + close(ptr->sockFdV4); + ptr->sockFdV4 = -1; } -#else - // Safety for my future "I forgot the ifdef guard" self - ptr->sockFd6 = -1; // IPv6 not supported in this build -#endif -} -void TcpServer_Start(tcp_server_t* ptr, int maxcons) { - if (!ptr || ptr->sockFd < 0 || maxcons <= 0 || ptr->isRunning) { + if (ptr->sockFd < 0 && ptr->sockFdV4 < 0) { return; } - if (listen(ptr->sockFd, maxcons) < 0) { - return; - } - -#ifdef USE_IPV6 - if (ptr->sockFd6 >= 0) { - if (listen(ptr->sockFd6, maxcons) < 0) { - close(ptr->sockFd6); - ptr->sockFd6 = -1; - } - } -#endif - pthread_mutex_lock(&ptr->clientsMutex); ptr->maxClients = (size_t)maxcons; @@ -279,7 +292,35 @@ void TcpServer_Start(tcp_server_t* ptr, int maxcons) { ptr->isRunning = 1; pthread_mutex_unlock(&ptr->clientsMutex); - if (pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, ptr) != 0) { + int anyThreadStarted = 0; + + if (ptr->sockFd >= 0) { + tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)malloc(sizeof(*args)); + if (args) { + args->serverPtr = ptr; + args->listenFd = ptr->sockFd; + if (pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, args) == 0) { + anyThreadStarted = 1; + } else { + free(args); + } + } + } + + if (ptr->sockFdV4 >= 0) { + tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)malloc(sizeof(*args)); + if (args) { + args->serverPtr = ptr; + args->listenFd = ptr->sockFdV4; + if (pthread_create(&ptr->svrThreadV4, NULL, TcpServer_threadprocess, args) == 0) { + anyThreadStarted = 1; + } else { + free(args); + } + } + } + + if (!anyThreadStarted) { pthread_mutex_lock(&ptr->clientsMutex); ptr->isRunning = 0; free(ptr->clientsArrPtr); @@ -302,19 +343,22 @@ void TcpServer_Stop(tcp_server_t* ptr) { ptr->sockFd = -1; } -#ifdef USE_IPV6 - if (ptr->sockFd6 >= 0) { - shutdown(ptr->sockFd6, SHUT_RDWR); - close(ptr->sockFd6); - ptr->sockFd6 = -1; + if (ptr->sockFdV4 >= 0) { + shutdown(ptr->sockFdV4, SHUT_RDWR); + close(ptr->sockFdV4); + ptr->sockFdV4 = -1; } -#endif if (ptr->svrThread != 0 && !pthread_equal(ptr->svrThread, pthread_self())) { pthread_join(ptr->svrThread, NULL); } ptr->svrThread = 0; + if (ptr->svrThreadV4 != 0 && !pthread_equal(ptr->svrThreadV4, pthread_self())) { + pthread_join(ptr->svrThreadV4, NULL); + } + ptr->svrThreadV4 = 0; + pthread_mutex_lock(&ptr->clientsMutex); size_t maxClients = ptr->maxClients; tcp_connection_t** local = ptr->clientsArrPtr; @@ -381,12 +425,6 @@ void TcpServer_KillClient(tcp_server_t* ptr, tcp_connection_t* cli) { so_linger.l_linger = 0; setsockopt(cli->sockFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); -#ifdef USE_IPV6 - if (cli->sockFd6 >= 0) { - setsockopt(cli->sockFd6, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); - } -#endif - TcpServer_Disconnect(ptr, cli); }