// // File: hzTcpClient.cpp // // Legal Notice: This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com) // // The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free // Software Foundation, either version 3 of the License, or any later version. // // The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR // A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses. //
// // Implimentation of the hzTcpClient class //
#include <cstdio> #include <string> #include <iostream> #include <fstream>
#include <unistd.h> #include <sys/socket.h> #include <sys/time.h> #include <netdb.h> #include <errno.h> #include <openssl/ssl.h> #include <openssl/err.h>
#include "hzProcess.h" #include "hzTcpClient.h"
hzEcode hzTcpClient::ConnectIP (const hzIpaddr& ipa, uint32_t nPort, uint32_t nTimeoutR, uint32_t nTimeoutS) { // Establish a standard, non-local, non-SSL TCP connection to a server at a known IP address. // // Arguments: 1) hostname The server name or IP address // 2) nPort The port number // 3) nTimoutR Socket option read timeout (default 30 seconds) // 4) nTimoutW Socket option write timeout (default 30 seconds) // // Returns: E_NOSOCKET If a socket could not be obtained // E_HOSTFAIL If no connection could be established or if socket options were not set. // E_OK If a connection to the host was established
_hzfunc("hzTcpClient::ConnectIP") ;
hzEcode rc = E_OK ; // Return code
// Check we are not already connected if (m_nSock) { //if (m_Hostname == hostname && m_nPort == nPort) if (m_nPort == nPort) return E_OK ;
m_Hostname.Clear() ; m_pHost = 0 ; Close() ; }
// Create the socket m_nPort = nPort ;
memset(&m_SvrAddr, 0, sizeof(m_SvrAddr)) ; m_SvrAddr.sin_family = AF_INET ; memcpy(&m_SvrAddr.sin_addr, m_pHost->h_addr, m_pHost->h_length) ; m_SvrAddr.sin_port = htons(nPort) ;
if ((m_nSock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return hzerr(E_NOSOCKET, "Could not create socket (errno=%d)", errno) ; // Connect as client to host if (connect(m_nSock, (SOCKADDR*) &m_SvrAddr, sizeof(m_SvrAddr)) < 0) return hzerr(E_HOSTFAIL, "Could not connect to host [%s] on port %d (errno=%d)", *m_Hostname, m_nPort, errno) ;
// Apply timeouts rc = SetRecvTimeout(nTimeoutR) ; if (rc == E_OK) rc = SetSendTimeout(nTimeoutR) ;
return E_OK ; }
hzEcode hzTcpClient::ConnectStd (const char* hostname, uint32_t nPort, uint32_t nTimeoutR, uint32_t nTimeoutS) { // Establish a standard, non-local, non-SSL TCP connection to a server. Note that to re-connect in the event of a drop-out, just call this function again. // A subsequent call will not repeat the DNS query unless it is to a different hostname. Note also that this function will do nothing if called with the // same hostname, no error flag has been set and the socket is non-zero. The error flag is set by send or recv errors. // // Arguments: 1) hostname The server name or IP address // 2) nPort The port number // 3) nTimoutR Socket option read timeout (default 30 seconds) // 4) nTimoutW Socket option write timeout (default 30 seconds) // // Returns: E_DNS_NOHOST If the domain does not exist // E_DNS_FAILED If the domain settings were invalid // E_DNS_NODATA If the domain exists but no server found // E_DNS_RETRY If the DNS was busy // E_NOSOCKET If a socket could not be obtained // E_HOSTFAIL If no connection could be established or if socket options were not set. // E_OK If a connection to the host was established
_hzfunc("hzTcpClient::ConnectStd") ;
hzEcode rc = E_OK ; // Return code
// Check we are not already connected if (m_nSock) { if (m_Hostname == hostname && m_nPort == nPort) return E_OK ;
m_Hostname.Clear() ; m_pHost = 0 ; Close() ; }
if (m_Hostname && m_Hostname != hostname) { // At the point of call there was no socket but this hzTcpClient instance has a hostname from a previous connection. If this differs from the host now // being sought then the m_pHost value will be invalid and a fresh call to gethostbyname is required.
m_Hostname = hostname ; m_pHost = 0 ; }
if (!m_Hostname) m_Hostname = hostname ;
// If we have not got the hostname from a previous connect, get the hostname now if (!m_pHost) { m_pHost = gethostbyname(hostname) ;
if (!m_pHost) { if (h_errno == TRY_AGAIN) return E_DNS_RETRY ; if (h_errno == HOST_NOT_FOUND) return E_DNS_NOHOST ; if (h_errno == NO_RECOVERY) return E_DNS_FAILED ;
if (h_errno == NO_DATA || h_errno == NO_ADDRESS) return E_DNS_NODATA ;
m_Hostname.Clear() ; return hzerr(E_DNS_NOHOST, "Unknown Host [%s]\n", hostname) ; } } // Create the socket
m_nPort = nPort ;
memset(&m_SvrAddr, 0, sizeof(m_SvrAddr)) ; m_SvrAddr.sin_family = AF_INET ; memcpy(&m_SvrAddr.sin_addr, m_pHost->h_addr, m_pHost->h_length) ; m_SvrAddr.sin_port = htons(nPort) ;
if ((m_nSock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return hzerr(E_NOSOCKET, "Could not create socket (errno=%d)", errno) ; // Connect stage if (connect(m_nSock, (SOCKADDR*) &m_SvrAddr, sizeof(m_SvrAddr)) < 0) return hzerr(E_HOSTFAIL, "Could not connect to host [%s] on port %d (errno=%d)", *m_Hostname, m_nPort, errno) ;
// Apply timeouts rc = SetRecvTimeout(nTimeoutR) ; if (rc == E_OK) rc = SetSendTimeout(nTimeoutR) ;
return E_OK ; }
hzEcode hzTcpClient::ConnectSSL (const char* hostname, uint32_t nPort, uint32_t nTimeoutR, uint32_t nTimeoutS) { // Purpose: Establish an SSL TCP connection to a server // // Arguments: 1) hostname The server name or IP address // 2) nPort The port number // 3) nTimeoutR Timeout (Recv) // 4) nTimeoutS Timeout (Send) // // Returns: E_DNS_NOHOST If the domain does not exist // E_DNS_FAILED If the domain settings were invalid // E_DNS_NODATA If the domain exists but no server found // E_DNS_RETRY If the DNS was busy // E_INITFAIL If the SSL client side settings fail // E_NOSOCKET If a socket could not be obtained // E_HOSTFAIL If no connection could be established or if socket options were not set. // E_OK If a connection to the host was established
_hzfunc("hzTcpClient::ConnectSSL") ;
static bool bBeenHere = false ; // OPENSSL init state
const SSL_METHOD* sslMethod = 0 ; // SSL client method SSL_CTX* sslCtx = 0 ; // SSL client CTX structure
int32_t sys_rc ; // Return from connect call hzEcode rc = E_OK ; // Return code
// Ensure SSL/TLS is initialized if (!bBeenHere) { #if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_library_init(); #else OPENSSL_init_ssl(0, NULL); #endif bBeenHere = true ; }
// Check we are not already connected if (m_nSock) { if (m_Hostname == hostname && m_nPort == nPort) return E_OK ;
m_Hostname.Clear() ; m_pHost = 0 ; Close() ; }
if (m_Hostname && m_Hostname != hostname) { // At the point of call there was no socket but this hzTcpClient instance has a hostname from a previous connection. If this differs from the host now // being sought then the m_pHost value will be invalid and a fresh call to gethostbyname is required.
m_Hostname = hostname ; m_pHost = 0 ; }
// Get the host IP m_pHost = gethostbyname(hostname) ;
if (!m_pHost) { threadLog("No Host found\n") ;
if (h_errno == TRY_AGAIN) return E_DNS_RETRY ; if (h_errno == HOST_NOT_FOUND) return E_DNS_NOHOST ; if (h_errno == NO_RECOVERY) return E_DNS_FAILED ;
if (h_errno == NO_DATA || h_errno == NO_ADDRESS) return E_DNS_NODATA ;
m_Hostname.Clear() ; return hzerr(E_DNS_NOHOST, "Unknown Host [%s]\n", hostname) ; }
m_Hostname = hostname ; m_nPort = nPort ;
// Create the socket memset(&m_SvrAddr, 0, sizeof(m_SvrAddr)) ; m_SvrAddr.sin_family = AF_INET ; memcpy(&m_SvrAddr.sin_addr, m_pHost->h_addr, m_pHost->h_length) ; m_SvrAddr.sin_port = htons(nPort) ;
if ((m_nSock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return hzerr(E_INITFAIL, "Could not create socket (returns %d, errno=%d)", m_nSock, errno) ; threadLog("Using socket %d\n", m_nSock) ;
// Establish a standard, non-SSL TCP/IP connection sys_rc = connect(m_nSock, (SOCKADDR*) &m_SvrAddr, sizeof(m_SvrAddr)) ; if (sys_rc < 0) { threadLog("Could not connect to host (returns %d)", sys_rc) ; return hzerr(E_HOSTFAIL, "Could not connect to host [%s] on port %d (errno=%d)", *m_Hostname, m_nPort, errno) ; } threadLog("Connected\n") ;
// Apply timeouts if (!nTimeoutR) nTimeoutR = 90 ; if (!nTimeoutS) nTimeoutS = 90 ; if (rc == E_OK) rc = SetRecvTimeout(nTimeoutR) ; if (rc == E_OK) rc = SetSendTimeout(nTimeoutS) ;
if (rc != E_OK) return hzerr(rc, "Could not set connection timeouts") ; threadLog("Socket options set\n") ;
/* ** SSL/TLS part */
sslMethod = TLS_client_method() ; if (!sslMethod) { Close() ; return hzerr(E_INITFAIL, "No SSL Client Method issued") ; } threadLog("Method set\n") ;
sslCtx = SSL_CTX_new(sslMethod) ; if (!sslCtx) { Close() ; hzerr(E_INITFAIL, "No SSL Structure issued") ; } threadLog("CTX Created\n") ;
m_pSSL = SSL_new(sslCtx) ; if (!m_pSSL) { threadLog("Could not allocate SSL structure\n") ; return E_HOSTFAIL ; } threadLog("SSL allocated\n") ;
/* if (SSL_CTX_set_cipher_list(sslCtx, "ECDHE-RSA-AES128-GCM-SHA256") <= 0) { threadLog("Error setting the cipher list.\n"); return E_HOSTFAIL ; } threadLog("Cipers Listed\n") ; */
sys_rc = SSL_set_fd(m_pSSL, m_nSock); if (sys_rc != 1) { threadLog("Could not set SSL file descriptor\n") ; return E_HOSTFAIL ; } threadLog("SSL fd set\n") ;
SSL_set_tlsext_host_name(m_pSSL, *m_Hostname) ;
SSL_set_connect_state(m_pSSL) ; threadLog("SSL set as client\n") ;
sys_rc = SSL_connect(m_pSSL); //sys_rc = SSL_do_handshake(m_pSSL); threadLog("Handshake done\n") ;
if (sys_rc != 1) { threadLog("Could not connect: %s\n", ShowErrorSSL(SSL_get_error(m_pSSL, sys_rc))) ; return E_HOSTFAIL ; } threadLog("Connected Secure\n") ;
/* Move to separate function or scrap if (bCheckCert) { if (SSL_get_peer_certificate(m_pSSL) != NULL) { if (SSL_get_verify_result(m_pSSL) == X509_V_OK) threadLog("Client verification with SSL_get_verify_result() succeeded.\n") ; else threadLog("Client verification with SSL_get_verify_result() failed.\n") ; } } */
return E_OK ; }
hzEcode hzTcpClient::ConnectLoc (uint32_t nPort) { // Purpose: Establish a UNIX domain TCP connection to a server // // Arguments: 1) nPort The port number // // Returns: E_NOSOCKET If the socket could not be created // E_HOSTFAIL If not connection can be established // E_OK If operation successfull
_hzfunc("hzTcpClient::ConnectLoc") ;
// Check we are not already connected if (m_nSock) { if (m_nPort == nPort) return E_OK ; Close() ; }
m_Hostname = "127.0.0.1" ; m_nPort = nPort ;
// Get the hostname if (!(m_pHost = gethostbyname(*m_Hostname))) { threadLog("Unknown Host [%s]\n", *m_Hostname) ; return E_HOSTFAIL ; } // Create the socket
memset(&m_SvrAddr, 0, sizeof(m_SvrAddr)) ; m_SvrAddr.sin_family = AF_UNIX ; memcpy(&m_SvrAddr.sin_addr, m_pHost->h_addr, m_pHost->h_length) ; m_SvrAddr.sin_port = htons(nPort) ;
if ((m_nSock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return hzerr(E_NOSOCKET, "Could not create socket (returns %d, errno %d)", m_nSock, errno) ; // Connect stage if (connect(m_nSock, (struct sockaddr *) &m_SvrAddr, sizeof(m_SvrAddr)) < 0) return hzerr(E_HOSTFAIL, "Could not connect to host (errno=%d)", errno) ;
if (m_nSock <= 0) return hzerr(E_HOSTFAIL, "Unspecified error. Socket is %d\n", m_nSock) ;
return E_OK ; }
hzEcode hzTcpClient::SetSendTimeout (uint32_t nInterval) { // Set the socket options so that the timeout for outgoing packets is set to the supplied interval // // Arguments: 1) nInterval Timeout interval in seconds // // Returns: E_HOSTFAIL If the timeout could not be set // E_OK If the timeout was successfully set
timeval tv ; // Timeout structure
tv.tv_sec = nInterval > 0 ? nInterval : 30 ; tv.tv_usec = 0 ;
if (setsockopt(m_nSock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) return E_HOSTFAIL ; return E_OK ; }
hzEcode hzTcpClient::SetRecvTimeout (uint32_t nInterval) { // Set the socket options so that the timeout for receiving packets is set to the supplied interval // // Arguments: 1) nInterval Timeout interval in seconds // // Returns: E_HOSTFAIL If the timeout could not be set // E_OK If the timeout was successfully set
struct timeval tv ; // Timeout structure
tv.tv_sec = nInterval > 0 ? nInterval : 30 ; tv.tv_usec = 0 ;
if (setsockopt(m_nSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) return E_HOSTFAIL ; return E_OK ; }
void hzTcpClient::Show (hzChain& Z) { // Diagnostic purposes. Show details for host if currently connected // // Arguments: 1) Z Chain to aggregate report to // // Returns: None
if (!m_pHost) Z << "hzTcpClient::Show: Not Connected\n" ; else { Z.Printf("hzTcpClient::Show: Connected to: %u.%u.%u.%u %s (type %d, len %d)\n", m_pHost->h_addr[0], m_pHost->h_addr[1], m_pHost->h_addr[2], m_pHost->h_addr[3], m_pHost->h_name, m_pHost->h_addrtype, m_pHost->h_length) ; Z.Printf(" (alias) %s\n", m_pHost->h_aliases[0]) ; Z.Printf(" (addr) %s\n", m_pHost->h_addr_list[0] + 4) ; Z << "------\n" ; } }
hzEcode hzTcpClient::Send (const void* pIn, uint32_t nLen) { // Purpose: Write buffer content to a socket // // Arguments: 1) pIn The buffer (void*) // 2) nLen Number of bytes to send // // Returns: E_NOSOCKET If the connection is not open // E_WRITEFAIL If the number of bytes written falls short of the intended number. In this event the connection is closed. // E_OK If the operation is successfull.
_hzfunc("hzTcpClient::Send(void*,uint32_t)") ;
uint32_t nSent ; // Bytes actually sent
if (!m_nSock) return E_NOSOCKET ; if (nLen == 0) return E_OK ;
if (m_pSSL) //nSent = SSL_write(m_pSSL, (const char*) pIn, nLen) ; nSent = SSL_write(m_pSSL, pIn, nLen) ; else //nSent = write(m_nSock, (const char*) pIn, nLen) ; nSent = write(m_nSock, pIn, nLen) ;
if (nSent == 0) { threadLog("Socket timed out while writing to server %s port %d socket %d", *m_Hostname, m_nPort, m_nSock) ; return E_TIMEOUT ; } if (nSent != nLen) { Close() ; threadLog("Socket write error to server %s port %d socket %d", *m_Hostname, m_nPort, m_nSock) ; return E_WRITEFAIL ; }
return E_OK ; }
hzEcode hzTcpClient::Send (const hzChain& C) { // Purpose: Write chain content to a socket // // Arguments: 1) C The chain to send. No size indicator is needed as whole chain is sent. // // Returns: E_NOSOCKET If the connection is not open // E_WRITEFAIL If the number of bytes written falls short of the intended number. In this event the connection is closed. // E_TIMEOUT If the operation timed out. // E_OK If the operation is successfull.
_hzfunc("hzTcpClient::Send(hzChain&)") ;
chIter ci ; // To iterate input chain char* i ; // To populate output buffer uint32_t nSend ; // Bytes to send in current packet uint32_t nSent ; // Bytes actually sent according to write operation uint32_t nTotal = 0 ; // Total sent so far hzEcode rc = E_OK ; // Return code
if (!m_nSock) return E_NOSOCKET ;
ci = C ;
for (; rc == E_OK && nTotal < C.Size() ;) { for (i = m_Buf, nSend = 0 ; !ci.eof() && nSend < HZ_MAXPACKET ; nSend++, ci++) *i++ = *ci ;
if (!nSend) break ;
// Do the send if (m_pSSL) nSent = SSL_write(m_pSSL, m_Buf, nSend) ; else nSent = write(m_nSock, m_Buf, nSend) ;
if (nSent < 0) rc = errno == ETIMEDOUT ? E_TIMEOUT : E_SENDFAIL ; else nTotal += nSent ; }
return rc ; }
hzEcode hzTcpClient::Recv (void* vpOut, uint32_t& nRecv, uint32_t nMax) { // Purpose: Read from a socket into a buffer. // // Arguments: 1) vpOut The buffer to populate // 2) nRecv A reference to number of bytes received // 3) nMax The maximum number of bytes to receive // // Returns: E_NOSOCKET If the connection has been closed // E_RECVFAIL If the socket read operation fails // E_OK If operation successfull
_hzfunc("hzTcpClient::Recv(1)") ;
char* cpOut ; // Buffer recast to char* int32_t nBytes ; // Bytes recieved in socket read
cpOut = (char*) vpOut ; cpOut[0] = 0 ;
nRecv = 0 ; if (!m_nSock) return E_NOSOCKET ; if (nMax == 0) return E_OK ;
if (m_pSSL) nBytes = SSL_read(m_pSSL, cpOut, nMax) ; else nBytes = recv(m_nSock, cpOut, nMax, 0) ;
if (nBytes < 0) { if (errno == EAGAIN) return E_OK ; if (errno == ETIMEDOUT) return E_TIMEOUT ;
//Close() ; return E_RECVFAIL ; }
nRecv = nBytes ; return E_OK ; }
hzEcode hzTcpClient::Recv (hzChain& Z) { // Purpose: Read from a socket into the supplied chain. The function terminates when no more bytes can be read from the socket. // // Argument: Z The chain to populate // // Returns: E_NOSOCKET If the connection has been closed // E_RECVFAIL If the socket read operation fails // E_MEMORY If there was insufficent memory to complete the operation. // E_OK If operation successfull
_hzfunc("hzTcpClient::Recv(2)") ;
int32_t nRecv ; // Bytes read by recv() or SSL_read()
if (!m_nSock) return E_NOSOCKET ;
for (;;) { if (m_pSSL) nRecv = SSL_read(m_pSSL, m_Buf, HZ_MAXPACKET) ; else nRecv = recv(m_nSock, m_Buf, HZ_MAXPACKET, 0) ;
if (!nRecv) break ;
if (nRecv < 0) { if (errno == EAGAIN) return E_TIMEOUT ; if (errno == ETIMEDOUT) return E_TIMEOUT ;
Close() ; return E_RECVFAIL ; }
Z.Append(m_Buf, nRecv) ; }
return E_OK ; }
void hzTcpClient::Close (void) { // Closes the client TCP connection. If there is an SSL connection, this is shutdown and then the handle for the SSL is freed. // // Arguments: None // Returns: None
if (m_pSSL) { SSL_shutdown(m_pSSL) ; SSL_free(m_pSSL) ; m_pSSL = 0 ; }
if (m_nSock) close(m_nSock) ; m_nSock = 0 ; }