#ifndef __TCPSocket_h__ #define __TCPSocket_h__ #include "Socket.h" #include "TLS.h" #include "TLSInfo.h" #include "IPAddress.h" #include namespace core { /// /// TCPSocket /// /// Provides a network TCP socket. /// /// For accessing TCP network functions use this object. The connection oriented nature of TCP /// provides a single client persistent connection with data error correction and a durable /// synchronous data connection. /// class TCPSocket : public Socket, public TLS { public: /// /// /// TCPSocket(EPoll &ePoll); /// /// /// TCPSocket(EPoll &ePoll, std::string text); /// /// /// TCPSocket(EPoll &ePoll, TLSInfo *tlsInfo, std::string text); /// /// /// TCPSocket(EPoll &ePoll, SSL_CTX *ctx, std::string text); /// /// /// virtual ~TCPSocket(); /// /// /// void connect(IPAddress &address); IPAddress ipAddress; /// /// The output method is called by a socket session (TCPSession) and /// will output the detail information for the client socket. When extending /// TCPSocket or TCPSession you can override the method to add attributes /// to the list. /// virtual void output(std::stringstream &out); /// /// Override this method to receive data directly from the socket as data is /// received. If you need data split by line termination characters then /// override the onLineReceived method instead. /// virtual void onDataReceived(coreutils::ZString &data) override; /// /// Override the onLineReceived method to receive a string of characters that /// represents a single line of data terminated by a LF or CRLF. If onDataReceived /// was overriden this method will not be called unless the onDataReceived calls /// this method explicitly using the class and member name. /// virtual void onLineReceived(coreutils::ZString &line); /// /// Override the onBlockReceived method to receive a string of characters that /// represents a single block of data of length determined by the block length value. If /// onDataReceived was overriden this method will not be called unless the onDataReceived /// calls this method explicitly using the class and member name. /// virtual void onBlockReceived(coreutils::ZString &block); /// /// Use setBlockSize to set the amount of data that should be read at once from the /// session data buffer. /// If this value is set to 0 then the data will be retrieved /// void setBlockSize(int size = 0); /// /// Dispatches to TLS::receiveData (SSL_read) when this socket was constructed with a /// TLSInfo/SSL_CTX, otherwise falls back to Socket::receiveData (plain ::read). Needed /// because Socket and TLS both declare receiveData as unrelated sibling methods - /// Socket::receiveData is virtual but TLS::receiveData does not override it, so without /// this explicit override TLS::receiveData would never be reached and encrypted bytes /// would be handed to onDataReceived as if they were plaintext. /// virtual void receiveData(coreutils::ZString &buffer) override; /// /// Dispatches to TLS::sendData (SSL_write) when this socket was constructed with a /// TLSInfo/SSL_CTX, otherwise falls back to Socket::sendData (plain ::write). Mirrors /// the receiveData override above for the write path. /// virtual int sendData(const char *data, int length) override; /// /// True only when this socket is actually TLS (ctx set) and the /// last sendData() call failed specifically with /// SSL_ERROR_WANT_READ - see TLS::lastSendWantedRead's own doc /// comment for why Socket::writeSocket()/resetSocket() need this. /// virtual bool sendNeedsRead() override; /// /// Completes the TLS handshake (SSL_new/SSL_set_fd/SSL_accept) for /// THIS socket's own ctx/descriptor. registerSocket()/acceptSocket() /// are protected TLS members, reachable here via an ordinary /// same-class call - the point of exposing this as a public wrapper /// is so a sibling class (TCPServer, which does not inherit /// TCPSocket-the-session's-particular-instance and so cannot reach /// another object's protected members directly) can still trigger /// the handshake on a specific accepted session from /// TCPServer::connectionRequest(), rather than accidentally running /// it against the listening server socket itself (see that call /// site's own comment for the real, confirmed crash this replaces - /// SSL_read() on an accepted session's never-initialized ssl /// member). /// void performTLSHandshake(); /// /// Set once, at construction, and never touched again - how long /// this specific socket has been open since accept()/connect(), /// regardless of whether anything has actually happened on it /// since. TCPServer::sweepStaleSessions() reads this (alongside /// hasReceivedData below) to find connections that completed a /// TCP handshake but then never progressed - a scanner/prober /// probing an open port, or a TLS handshake that never finished - /// which otherwise sit in TCPServer::sessions forever: nothing in /// the existing design ever revisits a socket that stops /// generating epoll events (see sweepStaleSessions()'s own doc /// comment for the full reasoning). /// std::chrono::steady_clock::time_point acceptedAt = std::chrono::steady_clock::now(); /// /// Flips to true the first time onDataReceived() sees a non-empty /// read - true for both a plaintext socket's raw bytes and a TLS /// session's already-decrypted ones, since both paths funnel /// through the same onDataReceived() override below. A session /// that reaches sweepStaleSessions()'s grace period with this /// still false has never had a real exchange of any kind, TLS /// handshake included (a stuck/incomplete SSL_accept() never /// surfaces anything to onDataReceived) - the specific, narrow /// condition that sweep treats as abandoned. Deliberately NOT /// reset afterward and never consulted again once true, so a /// legitimate client that sends something once and then goes /// idle for a long time (an ordinary connected-but-idle game /// session) is never at risk from this - only a connection that /// NEVER said anything at all within the grace window is. /// bool hasReceivedData = false; protected: bool term = false; TLSInfo *tlsInfo; private: char *lineBuffer = NULL; int lineBufferSize = 0; int lineLength = 0; int blockSize = 0; // Socket's own `lock` (guards receiveData) and `outlock` (guards // writeSocket/the fifo) are two DIFFERENT mutexes - a thread // handling EPOLLIN and a different thread handling EPOLLWRNORM for // the SAME connection can therefore call SSL_read and SSL_write on // the SAME `ssl*` concurrently. OpenSSL does not support that: an // SSL object must not be used by more than one thread at a time // (even in 1.1.0+/3.x, which dropped the old app-supplied // CRYPTO_set_locking_callback global-locking API entirely - that // API never protected concurrent use of one SSL object anyway, // only OpenSSL's own internal global state). Confirmed live via // dmesg: "traps: apiserver[...] general protection fault ... in // libssl.so.3" during ordinary interactive use. ioLock serializes // just the raw SSL_read/SSL_write calls themselves (see // receiveData/sendData below) - deliberately NOT the surrounding // request-handling, which already nests `lock` before `outlock` // (receiveData's call chain reaches Socket::write(), which takes // outlock) - wrapping any larger scope in ioLock+lock+outlock // together risks recreating that same ordering as a new cycle. std::mutex ioLock; }; } #endif