//--------------------------------------------------------------------- #include "sharedNetwork/FirstSharedNetwork.h" #include "TcpClient.h" #include "TcpServer.h" #include "Archive/Archive.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/Os.h" #include "sharedLog/Log.h" #include "sharedLog/NetLogConnection.h" #include "sharedNetwork/Connection.h" #include "sharedNetwork/ConfigSharedNetwork.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //----------------------------------------------------------------------- const unsigned long KEEPALIVE_MS = 1000; //----------------------------------------------------------------------- namespace TcpClientNamespace { std::set s_pendingConnectionSends; std::set s_tcpClients; bool s_installed = false; } using namespace TcpClientNamespace; //--------------------------------------------------------------------- TcpClient::TcpClient (int sockHandle, TcpServer * server) : m_socket(sockHandle), m_tcpServer(server), m_pendingSend(), m_connection(0), m_recvBuffer(0), m_recvBufferLength(0), m_remoteAddress(), m_refCount(0), m_connected (true), m_lastSendTime(0), m_bindPort(0), m_rawTCP( false ) { s_tcpClients.insert(this); setSockOptions(); struct sockaddr_in target; socklen_t namelen = sizeof(struct sockaddr_in); if (getpeername(sockHandle, reinterpret_cast(&target), &namelen) == 0) m_remoteAddress = target; struct sockaddr_in bindAddr; if(getsockname(sockHandle, reinterpret_cast(&bindAddr), &namelen) == 0) { m_bindPort = ntohs(bindAddr.sin_port); } } // ---------------------------------------------------------------------- TcpClient::TcpClient (const std::string & a, const unsigned short port) : m_socket(-1), m_tcpServer(0), m_connection(0), m_recvBuffer(0), m_recvBufferLength(0), m_remoteAddress(a, port), m_refCount(0), m_connected (false), m_lastSendTime(0), m_bindPort(0), m_rawTCP( false ) { FATAL(! s_installed, ("TcpClient is not installed!")); s_tcpClients.insert(this); setupSocket(); } //--------------------------------------------------------------------- TcpClient::~TcpClient() { FATAL(m_refCount > 0, ("Attempting to destroy a TcpClient while references still exist!")); std::set::iterator f = s_tcpClients.find(this); if(f != s_tcpClients.end()) s_tcpClients.erase(f); f = s_pendingConnectionSends.find(this); if(f != s_pendingConnectionSends.end()) s_pendingConnectionSends.erase(f); delete[] m_recvBuffer; if(m_socket != -1) close (m_socket); } //--------------------------------------------------------------------- void TcpClient::addRef() { m_refCount++; } //--------------------------------------------------------------------- std::string const &TcpClient::getRemoteAddress() const { return m_remoteAddress.getHostAddress(); } //--------------------------------------------------------------------- unsigned short TcpClient::getRemotePort() const { return m_remoteAddress.getHostPort(); } //--------------------------------------------------------------------- void TcpClient::setSockOptions() { int optval = 1; setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); ioctl(m_socket, FIONBIO, &optval); } //--------------------------------------------------------------------- void TcpClient::commit() { int sent = 0; const unsigned char * ptr = m_pendingSend.getBuffer(); const unsigned char * const end = ptr + m_pendingSend.getSize(); static Archive::ByteStream s_localScratchBuffer; s_localScratchBuffer.clear(); int retries = 0; static const int maxRetries = ConfigSharedNetwork::getMaxTCPRetries(); bool sentSomething = false; do { // attempt to push pending data to socket sent = ::send (m_socket, ptr, end - ptr, MSG_NOSIGNAL); if(sent == -1) { // failed switch(errno) { // because it would block on a non-blocking socket case EAGAIN: case ENOBUFS: case ENOMEM: case EINTR: { retries++; // requeue break; } // an unhandled error (connection reset, for example) default: { m_pendingSend.clear(); perror("send: "); if (m_connection) m_connection->setDisconnectReason("TcpClient::commit send returned -1, errno=%d", errno); onConnectionClosed(); return; break; } } } else if(sent > 0) { sentSomething=true; ptr += sent; if(sent < end - ptr) { // data was passed to the socket, but not all // of it could be committed, keep trying! retries = 0; Os::sleep(1); } } else if(sent == 0) { // tried to send and got nothing through (should have returned an error, but be thorough) retries ++; Os::sleep(1); } } while(ptr < end && retries < maxRetries); if(ptr == end) { // all data was committed, clear pending buffer m_pendingSend.clear(); } else { // bailed out before all data was committed, save unsent data for // next pass on commit() if (sentSomething) { s_localScratchBuffer.put(ptr, end - ptr); m_pendingSend.clear(); m_pendingSend.put(s_localScratchBuffer.getBuffer(), s_localScratchBuffer.getSize()); } s_pendingConnectionSends.insert(this); } // clean up local accounting s_localScratchBuffer.clear(); } //--------------------------------------------------------------------- void TcpClient::flush() { if (m_connected && m_pendingSend.getSize() > 0) { // flush pending buffer commit(); } } //----------------------------------------------------------------------- void TcpClient::flushPendingWrites() { std::set sends = s_pendingConnectionSends; s_pendingConnectionSends.clear(); for (std::set::const_iterator i = sends.begin(); i != sends.end(); ++i) { TcpClient * const c = *i; if (c->m_connected) { c->flush(); if ( c->m_connection && c->m_pendingSend.getSize() && ConfigSharedNetwork::getLogSendingTooMuchData() && !dynamic_cast(c->m_connection)) { WARNING(true, ("(%s) Failed to commit %d bytes this frame to %s:%d. This process is sending more data than the remote can receive!", c->m_connection->getConnectionDescription().c_str(), c->m_pendingSend.getSize(), c->getRemoteAddress().c_str(), c->getRemotePort())); LOG("Network", ("(%s) Failed to commit %d bytes this frame to %s:%d. This process is sending more data than the remote can receive!", c->m_connection->getConnectionDescription().c_str(), c->m_pendingSend.getSize(), c->getRemoteAddress().c_str(), c->getRemotePort())); } } } } //--------------------------------------------------------------------- unsigned short TcpClient::getBindPort() const { return m_bindPort; } //--------------------------------------------------------------------- int TcpClient::getSocket() const { return m_socket; } //--------------------------------------------------------------------- void TcpClient::install() { FATAL(s_installed, ("TcpClient is already installed!")); s_installed = true; signal(SIGPIPE, SIG_IGN); } //--------------------------------------------------------------------- void TcpClient::onConnectionClosed() { if (m_connection) m_connection->setDisconnectReason("TcpClient::onConnectionClosed called"); m_connected = false; shutdown(m_socket, SHUT_RDWR); if(m_tcpServer) { m_tcpServer->removeClient(this); } close(m_socket); m_socket = -1; if(m_connection) { NetworkHandler::onTerminate(m_connection); } } //--------------------------------------------------------------------- void TcpClient::onConnectionOpened() { // Get the local port this socket was bound to. If // a client socket repeatedly attempts to connect to a local // service that should be listening on a port that the operating // system may assign AND the service isn't actually running and // using the port, the client socket may connect to itself. // For example, A client connecting to 127.0.0.1:50005, the operating // system may assign the client socket port 50005 and the connection // succeeds, but it is in fact a connection to itself, meaning the // client isn't actually talking to any valid service. struct sockaddr_in bindAddr; socklen_t addrLen = sizeof(struct sockaddr_in); if(getsockname(m_socket, reinterpret_cast(&bindAddr), &addrLen) == 0) { m_bindPort = ntohs(bindAddr.sin_port); } // this *might* be a valid connection to a remote address, but // to easily catch all cases of loopback connections, attempt a // reconnect if the remote port is identical to the local port if(m_remoteAddress.getHostPort() == m_bindPort && ! m_tcpServer) { shutdown(m_socket, SHUT_RDWR); DEBUG_FATAL(m_tcpServer, ("Detected a loopback client connection on a TCP SERVER!!!")); close(m_socket); m_socket = -1; // reconnect the socket // address and port info is initialized in the constructor setupSocket(); LOG("Network", ("Detected a loopback, closing socket and reconnecting")); } else { m_connected = true; if(m_connection) { m_connection->onConnectionOpened(); } flush(); } } //--------------------------------------------------------------------- void TcpClient::onReceive (const unsigned char * const recvBuf, const int bytes) { if(m_connection) { m_connection->receive(recvBuf, bytes); } } //--------------------------------------------------------------------- void TcpClient::queryConnect () { struct pollfd pfd; pfd.fd = m_socket; pfd.events = POLLOUT; pfd.revents = 0; int pollResult = poll(&pfd, 1, 3000); if(pollResult > 0) { if (pfd.revents & POLLERR) { if (m_connection) m_connection->setDisconnectReason("TcpClient::queryConnect POLLERR"); onConnectionClosed(); } else if (pfd.revents & POLLHUP) { if (m_connection) m_connection->setDisconnectReason("TcpClient::queryConnect POLLHUP"); onConnectionClosed(); } else { onConnectionOpened(); } } } //----------------------------------------------------------------------- void TcpClient::release() { m_refCount--; if(m_refCount == 0) { if (m_connected) { if (m_connection) m_connection->setDisconnectReason("TcpClient::release called"); onConnectionClosed(); } delete this; } } //----------------------------------------------------------------------- void TcpClient::remove() { FATAL(!s_installed, ("TcpClient already removed!")); std::set::iterator i; for(i = s_tcpClients.begin(); i != s_tcpClients.end(); ++i) { TcpClient * c = (*i); c->release(); } s_installed = false; } //--------------------------------------------------------------------- void TcpClient::send(const unsigned char * const buffer, const int bufferLen) { if (bufferLen) { m_lastSendTime = Clock::getFrameStartTimeMs(); s_pendingConnectionSends.insert(this); if( !m_rawTCP ) Archive::put(m_pendingSend, bufferLen); m_pendingSend.put(buffer, bufferLen); static int const tcpMinimumFrame = ConfigSharedNetwork::getTcpMinimumFrame(); if (static_cast(m_pendingSend.getSize()) >= tcpMinimumFrame) flush(); } } //----------------------------------------------------------------------- void TcpClient::setConnection(Connection * c) { m_connection = c; } //--------------------------------------------------------------------- void TcpClient::checkKeepalive() { if (m_connected) { unsigned long const timeNow = Clock::getFrameStartTimeMs(); if (timeNow-m_lastSendTime > KEEPALIVE_MS) { m_lastSendTime = timeNow; s_pendingConnectionSends.insert(this); Archive::put(m_pendingSend, 0); } } } //--------------------------------------------------------------------- void TcpClient::update() { addRef(); if(!m_connected) { queryConnect(); } if(m_connected) { struct pollfd pfd; pfd.fd = m_socket; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; int pollResult = poll (&pfd, 1, 3000); // POLLERR is mutually exclusive with POLLIN and POLLHUP. // POLLIN and POLLHUP are not consistent cross-platform. Additionally, // a POLLHUP doesn't mean that there's not data available. The best, // cross-platform way to check for disconnection is to see if either // POLLHUP or POLLIN are set, and then read from the socket. // In cases where there's data, we'll just end up checking it next // frame anyway. In cases where there's no data, it means we've // disconnected so we can handle cleanup. if (pollResult) { if (m_recvBuffer == nullptr) { m_recvBufferLength = 1500; m_recvBuffer = new unsigned char [m_recvBufferLength]; } int bytesReceived = recv(m_socket, m_recvBuffer, m_recvBufferLength, 0); if (bytesReceived > 0) { onReceive(m_recvBuffer, bytesReceived); } else if (bytesReceived == -1) { switch (errno) { case EAGAIN: case EINTR: break; default: if (m_connection) m_connection->setDisconnectReason("TcpClient::update recv returned -1, errno=%d", errno); onConnectionClosed(); break; } } else if (bytesReceived == 0) { // peer closed connection if (m_connection) { // Log this, as it's a pretty deep change. LOG("Network", ("(%s) Read zero bytes on a successful poll (POLLIN|POLLHUP). Closing socket since we think it is closed.", m_connection->getConnectionDescription().c_str())); m_connection->setDisconnectReason("TcpClient::update recv returned 0"); } else { LOG("Network", ("(nullptr connection object) Read zero bytes on a successful poll (POLLIN|POLLHUP). Closing socket since we think it is closed.")); } onConnectionClosed(); } if (bytesReceived == m_recvBufferLength) { // grow the buffer delete [] m_recvBuffer; m_recvBufferLength = m_recvBufferLength * 2; m_recvBuffer = new unsigned char [m_recvBufferLength]; } } } release(); } // ---------------------------------------------------------------------- void TcpClient::setupSocket() { FATAL(! s_installed, ("TcpClient is not installed!")); protoent * p = getprotobyname ("tcp"); const int entry = p->p_proto; m_socket = socket (AF_INET, SOCK_STREAM, entry); if (m_socket != -1) { setSockOptions(); int nameLen = sizeof (struct sockaddr_in); int result; result = connect (m_socket, reinterpret_cast (&m_remoteAddress.getSockAddr4 () ), nameLen); if(result == -1) { switch(errno) { case EINPROGRESS: case EALREADY: break; default: perror("connect "); if (m_connection) m_connection->setDisconnectReason("TcpClient::TcpClient connect returned -1, errno=%d", errno); onConnectionClosed(); break; } } else { struct sockaddr_in bindAddr; socklen_t addrLen = sizeof(struct sockaddr_in); if(getsockname(m_socket, reinterpret_cast(&bindAddr), &addrLen) == 0) { m_bindPort = ntohs(bindAddr.sin_port); } } } } // ---------------------------------------------------------------------- void TcpClient::setRawTCP( bool bNewValue ) { m_rawTCP = bNewValue; } // ---------------------------------------------------------------------- void TcpClient::clearTcpServer() { m_tcpServer = 0; } // ======================================================================