Loading .gitignore +11 −1 Original line number Diff line number Diff line Loading @@ -8,3 +8,13 @@ build/ .vscode/ test/ build_ninja/ install/ curl_out.txt test_https_ps.ps1 patch_ssl_accept_run.py patch_ssl_accept.py refactor_ssl_key.py reproduce_issue.cpp silence_logs.py test_openssl.sh test_tls_client.py src/event/iocp.cpp +31 −8 Original line number Diff line number Diff line Loading @@ -135,6 +135,12 @@ namespace netplus { throw e; } if (serverSock->fd() == INVALID_SOCKET) { NetException e; e[NetException::Critical] << "init_state: Server socket is INVALID_SOCKET (closed?). Object lifetime issue?"; throw e; } // bind listening socket HANDLE h = CreateIoCompletionPort((HANDLE)serverSock->fd(), st->iocp, Loading @@ -161,7 +167,12 @@ namespace netplus { try { // Uses the virtual accept which uses internal buffer serverSock->accept(st->fnAcceptEx, ctx->csock); } catch (NetException& e) { std::cerr << "[IOCP] post_accept failed (NetException): " << e.what() << std::endl; delete ctx; return; } catch (...) { std::cerr << "[IOCP] post_accept failed (unknown exception)" << std::endl; delete ctx; return; } Loading Loading @@ -315,8 +326,9 @@ namespace netplus { void operator()(event* ev, socket* serverSock, int tid) { EventState* st = ST(ev); try { while (event::Running) { std::cerr << "[IOCP] Worker " << tid << " waiting..." << std::endl; // std::cerr << "[IOCP] Worker " << tid << " waiting..." << std::endl; DWORD bytes = 0; ULONG_PTR key = 0; Loading @@ -324,15 +336,15 @@ namespace netplus { BOOL ok = GetQueuedCompletionStatus(st->iocp, &bytes, &key, &pov, INFINITE); DWORD lastErr = GetLastError(); std::cerr << "[IOCP] Worker " << tid << " got completion: ok=" << ok << " bytes=" << bytes << " key=" << key << " pov=" << pov << " err=" << lastErr << std::endl; // std::cerr << "[IOCP] Worker " << tid << " got completion: ok=" << ok << " bytes=" << bytes << " key=" << key << " pov=" << pov << " err=" << lastErr << std::endl; if (!event::Running) break; if (!pov && key == 0) continue; // wake packet // 1) Accept completion? std::cerr << "[IOCP] Checking if AcceptEx completion, pov=" << pov << std::endl; // std::cerr << "[IOCP] Checking if AcceptEx completion, pov=" << pov << std::endl; if (AcceptCtx* actx = pop_accept(st, pov)) { std::cerr << "[IOCP] AcceptEx completion detected!" << std::endl; // std::cerr << "[IOCP] AcceptEx completion detected!" << std::endl; // Take ownership of the accepted socket std::unique_ptr<socket> acceptedSock = std::move(actx->csock); Loading Loading @@ -421,7 +433,12 @@ namespace netplus { continue; } con* owner = conPtr.get(); std::cerr << "[IOCP] Found connection, operation=" << (buf->operation == OP_READ ? "READ" : "WRITE") << " bytes=" << bytes << std::endl; std::cerr << "[IOCP] Found connection, operation=" << (buf->operation == OP_READ ? "READ" : "WRITE") << " bytes=" << bytes << " InternalHigh=" << buf->overlapped.InternalHigh << std::endl; if (buf->operation == OP_READ && buf->overlapped.InternalHigh == 0 && bytes > 0) { std::cerr << "[IOCP] Fixup: setting InternalHigh to " << bytes << std::endl; buf->overlapped.InternalHigh = bytes; } if (!ok) { // error -> disconnect Loading Loading @@ -645,6 +662,12 @@ namespace netplus { // if (buf) delete buf; } } } catch (std::exception& e) { std::cerr << "[IOCP] EventWorker thread exception: " << e.what() << std::endl; } catch (...) { std::cerr << "[IOCP] EventWorker thread unknown exception" << std::endl; } } }; Loading Loading @@ -704,7 +727,7 @@ namespace netplus { _pollFD = (int)(intptr_t)st->iocp; Running = true; std::cerr << "[IOCP] XXXXX Starting event loop, Running=" << Running.load() << " threads=" << st->threads << std::endl; std::cerr.flush(); std::cerr << "[IOCP] runEventloop: Starting " << st->threads << " threads." << std::endl; // Pre-post accepts (like "listen ready") // recommended: 2*threads AcceptEx in flight Loading src/socket.h +1 −0 Original line number Diff line number Diff line Loading @@ -331,6 +331,7 @@ namespace netplus { #endif bool loadServerPrivateKeyDer(const std::string& keyDerPath); bool loadServerPrivateKey(const std::vector<uint8_t>& keyData); bool hasPendingWrite() const override { return (!_send_queue.empty()) || (_send_off < _send_record.size()); Loading src/ssl.cpp +27 −63 Original line number Diff line number Diff line Loading @@ -357,9 +357,9 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() throw e; }; // ------------------------------------------------------------ // -------- -------------------------------------------------------- // 1) ensure we have at least TLS record header (5 bytes) // ------------------------------------------------------------ // ---------------------------------------------------------------- #ifdef Windows while (_rx_tcp_buf.size() < 5) { buffer tmpBuf(4096); Loading Loading @@ -407,7 +407,6 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() // ------------------------------------------------------------ uint16_t recLen = (uint16_t(_rx_tcp_buf[3]) << 8) | uint16_t(_rx_tcp_buf[4]); size_t total = 5 + recLen; // ------------------------------------------------------------ // 3) read until full record present // ------------------------------------------------------------ Loading Loading @@ -1553,6 +1552,7 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ void netplus::ssl::handshake_after_accept(){ std::cerr << "[SSL] handshake_after_accept called, _handshakeDone=" << _handshakeDone << " _hs_tx.size()=" << _hs_tx.size() << std::endl; auto throwSSL = [&](int type, const std::string& msg) { netplus::NetException e; e[type] << "ssl::accept: " << msg; Loading Loading @@ -2669,68 +2669,29 @@ void netplus::ssl::flush_out(){ #ifdef Windows void netplus::ssl::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { NetException exception; // Create the SSL socket if it doesn't exist if (!csock) { if(this->_cert) csock = std::make_unique<netplus::ssl>(*this->_cert); else{ exception[NetException::Error] << "ssl::accept: no certificate"; throw exception; } ssl* cssock = dynamic_cast<ssl*>(csock.get()); if (!cssock) { NetException e; e[NetException::Critical] << "ssl::accept: csock is not ssl"; throw e; } ssl* cssock = static_cast<ssl*>(csock.get()); // Reset TLS state for new connection cssock->resetTLS(); // SSL-Status zurücksetzen cssock->_handshakeDone = false; cssock->_hs_tx.clear(); cssock->_rx_tcp_buf.clear(); // Share cert/rsa/ec key from server socket cssock->_cert = this->_cert; cssock->_rsa = this->_rsa; std::memcpy(cssock->_ec_priv, this->_ec_priv, 32); cssock->_has_ec_key = this->_has_ec_key; const addrinfo* ai = reinterpret_cast<const addrinfo*>(this->_SocketInfo); // Socket erstellen cssock->_Socket = WSASocketW(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (cssock->_Socket == INVALID_SOCKET) { exception[NetException::Error] << "AcceptEx (SSL): WSASocket failed: " << WSAGetLastError(); throw exception; } if (!csock->_ReadBuffer) csock->_ReadBuffer.reset(new buffer); buffer* buf = csock->_ReadBuffer.get(); // WICHTIG: Verwende die Overlapped-Struktur aus dem übergebenen Buffer-Objekt // Nur so kann der EventWorker via CONTAINING_RECORD das buffer-Objekt finden! std::memset(&buf->overlapped, 0, sizeof(WSAOVERLAPPED)); buf->operation = OP_ACCEPT; // Markiere die Operation für den Worker DWORD dwbytes = 0; // AcceptEx Aufruf const BOOL ok = lpfnAcceptEx( (SOCKET)this->_Socket, (SOCKET)cssock->_Socket, buf->data.buf, 0, // 0 = Return sofort bei Verbindung (TCP Handshake) sizeof(SOCKADDR_STORAGE) + 16, sizeof(SOCKADDR_STORAGE) + 16, &dwbytes, &buf->overlapped // <--- Entscheidend für den Worker ); if (!ok && WSAGetLastError() != WSA_IO_PENDING) { closesocket((SOCKET)cssock->_Socket); cssock->_Socket = INVALID_SOCKET; exception[NetException::Error] << "AcceptEx (SSL) failed: " << WSAGetLastError(); throw exception; } // Call parent tcp::accept() to handle AcceptEx and socket creation tcp::accept(lpfnAcceptEx, csock); } #endif Loading Loading @@ -3885,13 +3846,16 @@ static bool isOidEcPublicKey(const uint8_t* oid, size_t len) { } bool netplus::ssl::loadServerPrivateKeyDer(const std::string& keyDerPath) { std::cerr << "[SSL] *** loadServerPrivateKeyDer CALLED ***" << std::endl; std::cerr << "[SSL] keyDerPath=" << keyDerPath << std::endl; std::cerr.flush(); try { std::vector<uint8_t> der = readFileBytesDer(keyDerPath); std::cerr << "[SSL] Read " << der.size() << " bytes from key file" << std::endl; std::cerr.flush(); return loadServerPrivateKey(der); } catch (...) { throw; } } bool netplus::ssl::loadServerPrivateKey(const std::vector<uint8_t>& der) { try { netplus::ASN1Node root; size_t used = _cert->parseInternal(der.data(), der.size(), root); // reuse parser Loading src/windows/tcp.cpp +9 −2 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ tcp::tcp(const std::string& addr, int port, int maxconnections, int sockopts) } for (addrinfo* rp = result; rp; rp = rp->ai_next) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); _Socket = ::WSASocketA(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (_Socket == INVALID_SOCKET) continue; Loading Loading @@ -228,7 +228,7 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } if (_Socket == INVALID_SOCKET) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); _Socket = ::WSASocketA(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (_Socket == INVALID_SOCKET) continue; } Loading Loading @@ -296,6 +296,9 @@ void tcp::setBlock() { } void tcp::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { if (!csock) { csock = std::make_unique<tcp>(); } // Create accept socket SOCKET acceptSock = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED); if (acceptSock == INVALID_SOCKET) { Loading @@ -304,6 +307,10 @@ void tcp::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { throw e; } // IOCP sockets should be non-blocking to prevent accidental blocking in recv/send u_long mode = 1; ::ioctlsocket(acceptSock, FIONBIO, &mode); if (!csock->_ReadBuffer) csock->_ReadBuffer.reset(new buffer); Loading Loading
.gitignore +11 −1 Original line number Diff line number Diff line Loading @@ -8,3 +8,13 @@ build/ .vscode/ test/ build_ninja/ install/ curl_out.txt test_https_ps.ps1 patch_ssl_accept_run.py patch_ssl_accept.py refactor_ssl_key.py reproduce_issue.cpp silence_logs.py test_openssl.sh test_tls_client.py
src/event/iocp.cpp +31 −8 Original line number Diff line number Diff line Loading @@ -135,6 +135,12 @@ namespace netplus { throw e; } if (serverSock->fd() == INVALID_SOCKET) { NetException e; e[NetException::Critical] << "init_state: Server socket is INVALID_SOCKET (closed?). Object lifetime issue?"; throw e; } // bind listening socket HANDLE h = CreateIoCompletionPort((HANDLE)serverSock->fd(), st->iocp, Loading @@ -161,7 +167,12 @@ namespace netplus { try { // Uses the virtual accept which uses internal buffer serverSock->accept(st->fnAcceptEx, ctx->csock); } catch (NetException& e) { std::cerr << "[IOCP] post_accept failed (NetException): " << e.what() << std::endl; delete ctx; return; } catch (...) { std::cerr << "[IOCP] post_accept failed (unknown exception)" << std::endl; delete ctx; return; } Loading Loading @@ -315,8 +326,9 @@ namespace netplus { void operator()(event* ev, socket* serverSock, int tid) { EventState* st = ST(ev); try { while (event::Running) { std::cerr << "[IOCP] Worker " << tid << " waiting..." << std::endl; // std::cerr << "[IOCP] Worker " << tid << " waiting..." << std::endl; DWORD bytes = 0; ULONG_PTR key = 0; Loading @@ -324,15 +336,15 @@ namespace netplus { BOOL ok = GetQueuedCompletionStatus(st->iocp, &bytes, &key, &pov, INFINITE); DWORD lastErr = GetLastError(); std::cerr << "[IOCP] Worker " << tid << " got completion: ok=" << ok << " bytes=" << bytes << " key=" << key << " pov=" << pov << " err=" << lastErr << std::endl; // std::cerr << "[IOCP] Worker " << tid << " got completion: ok=" << ok << " bytes=" << bytes << " key=" << key << " pov=" << pov << " err=" << lastErr << std::endl; if (!event::Running) break; if (!pov && key == 0) continue; // wake packet // 1) Accept completion? std::cerr << "[IOCP] Checking if AcceptEx completion, pov=" << pov << std::endl; // std::cerr << "[IOCP] Checking if AcceptEx completion, pov=" << pov << std::endl; if (AcceptCtx* actx = pop_accept(st, pov)) { std::cerr << "[IOCP] AcceptEx completion detected!" << std::endl; // std::cerr << "[IOCP] AcceptEx completion detected!" << std::endl; // Take ownership of the accepted socket std::unique_ptr<socket> acceptedSock = std::move(actx->csock); Loading Loading @@ -421,7 +433,12 @@ namespace netplus { continue; } con* owner = conPtr.get(); std::cerr << "[IOCP] Found connection, operation=" << (buf->operation == OP_READ ? "READ" : "WRITE") << " bytes=" << bytes << std::endl; std::cerr << "[IOCP] Found connection, operation=" << (buf->operation == OP_READ ? "READ" : "WRITE") << " bytes=" << bytes << " InternalHigh=" << buf->overlapped.InternalHigh << std::endl; if (buf->operation == OP_READ && buf->overlapped.InternalHigh == 0 && bytes > 0) { std::cerr << "[IOCP] Fixup: setting InternalHigh to " << bytes << std::endl; buf->overlapped.InternalHigh = bytes; } if (!ok) { // error -> disconnect Loading Loading @@ -645,6 +662,12 @@ namespace netplus { // if (buf) delete buf; } } } catch (std::exception& e) { std::cerr << "[IOCP] EventWorker thread exception: " << e.what() << std::endl; } catch (...) { std::cerr << "[IOCP] EventWorker thread unknown exception" << std::endl; } } }; Loading Loading @@ -704,7 +727,7 @@ namespace netplus { _pollFD = (int)(intptr_t)st->iocp; Running = true; std::cerr << "[IOCP] XXXXX Starting event loop, Running=" << Running.load() << " threads=" << st->threads << std::endl; std::cerr.flush(); std::cerr << "[IOCP] runEventloop: Starting " << st->threads << " threads." << std::endl; // Pre-post accepts (like "listen ready") // recommended: 2*threads AcceptEx in flight Loading
src/socket.h +1 −0 Original line number Diff line number Diff line Loading @@ -331,6 +331,7 @@ namespace netplus { #endif bool loadServerPrivateKeyDer(const std::string& keyDerPath); bool loadServerPrivateKey(const std::vector<uint8_t>& keyData); bool hasPendingWrite() const override { return (!_send_queue.empty()) || (_send_off < _send_record.size()); Loading
src/ssl.cpp +27 −63 Original line number Diff line number Diff line Loading @@ -357,9 +357,9 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() throw e; }; // ------------------------------------------------------------ // -------- -------------------------------------------------------- // 1) ensure we have at least TLS record header (5 bytes) // ------------------------------------------------------------ // ---------------------------------------------------------------- #ifdef Windows while (_rx_tcp_buf.size() < 5) { buffer tmpBuf(4096); Loading Loading @@ -407,7 +407,6 @@ std::vector<uint8_t> netplus::ssl::readTlsRecordAsync() // ------------------------------------------------------------ uint16_t recLen = (uint16_t(_rx_tcp_buf[3]) << 8) | uint16_t(_rx_tcp_buf[4]); size_t total = 5 + recLen; // ------------------------------------------------------------ // 3) read until full record present // ------------------------------------------------------------ Loading Loading @@ -1553,6 +1552,7 @@ void netplus::ssl::_tls13_send_finished(bool handshake_keys){ void netplus::ssl::handshake_after_accept(){ std::cerr << "[SSL] handshake_after_accept called, _handshakeDone=" << _handshakeDone << " _hs_tx.size()=" << _hs_tx.size() << std::endl; auto throwSSL = [&](int type, const std::string& msg) { netplus::NetException e; e[type] << "ssl::accept: " << msg; Loading Loading @@ -2669,68 +2669,29 @@ void netplus::ssl::flush_out(){ #ifdef Windows void netplus::ssl::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { NetException exception; // Create the SSL socket if it doesn't exist if (!csock) { if(this->_cert) csock = std::make_unique<netplus::ssl>(*this->_cert); else{ exception[NetException::Error] << "ssl::accept: no certificate"; throw exception; } ssl* cssock = dynamic_cast<ssl*>(csock.get()); if (!cssock) { NetException e; e[NetException::Critical] << "ssl::accept: csock is not ssl"; throw e; } ssl* cssock = static_cast<ssl*>(csock.get()); // Reset TLS state for new connection cssock->resetTLS(); // SSL-Status zurücksetzen cssock->_handshakeDone = false; cssock->_hs_tx.clear(); cssock->_rx_tcp_buf.clear(); // Share cert/rsa/ec key from server socket cssock->_cert = this->_cert; cssock->_rsa = this->_rsa; std::memcpy(cssock->_ec_priv, this->_ec_priv, 32); cssock->_has_ec_key = this->_has_ec_key; const addrinfo* ai = reinterpret_cast<const addrinfo*>(this->_SocketInfo); // Socket erstellen cssock->_Socket = WSASocketW(ai->ai_family, ai->ai_socktype, ai->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (cssock->_Socket == INVALID_SOCKET) { exception[NetException::Error] << "AcceptEx (SSL): WSASocket failed: " << WSAGetLastError(); throw exception; } if (!csock->_ReadBuffer) csock->_ReadBuffer.reset(new buffer); buffer* buf = csock->_ReadBuffer.get(); // WICHTIG: Verwende die Overlapped-Struktur aus dem übergebenen Buffer-Objekt // Nur so kann der EventWorker via CONTAINING_RECORD das buffer-Objekt finden! std::memset(&buf->overlapped, 0, sizeof(WSAOVERLAPPED)); buf->operation = OP_ACCEPT; // Markiere die Operation für den Worker DWORD dwbytes = 0; // AcceptEx Aufruf const BOOL ok = lpfnAcceptEx( (SOCKET)this->_Socket, (SOCKET)cssock->_Socket, buf->data.buf, 0, // 0 = Return sofort bei Verbindung (TCP Handshake) sizeof(SOCKADDR_STORAGE) + 16, sizeof(SOCKADDR_STORAGE) + 16, &dwbytes, &buf->overlapped // <--- Entscheidend für den Worker ); if (!ok && WSAGetLastError() != WSA_IO_PENDING) { closesocket((SOCKET)cssock->_Socket); cssock->_Socket = INVALID_SOCKET; exception[NetException::Error] << "AcceptEx (SSL) failed: " << WSAGetLastError(); throw exception; } // Call parent tcp::accept() to handle AcceptEx and socket creation tcp::accept(lpfnAcceptEx, csock); } #endif Loading Loading @@ -3885,13 +3846,16 @@ static bool isOidEcPublicKey(const uint8_t* oid, size_t len) { } bool netplus::ssl::loadServerPrivateKeyDer(const std::string& keyDerPath) { std::cerr << "[SSL] *** loadServerPrivateKeyDer CALLED ***" << std::endl; std::cerr << "[SSL] keyDerPath=" << keyDerPath << std::endl; std::cerr.flush(); try { std::vector<uint8_t> der = readFileBytesDer(keyDerPath); std::cerr << "[SSL] Read " << der.size() << " bytes from key file" << std::endl; std::cerr.flush(); return loadServerPrivateKey(der); } catch (...) { throw; } } bool netplus::ssl::loadServerPrivateKey(const std::vector<uint8_t>& der) { try { netplus::ASN1Node root; size_t used = _cert->parseInternal(der.data(), der.size(), root); // reuse parser Loading
src/windows/tcp.cpp +9 −2 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ tcp::tcp(const std::string& addr, int port, int maxconnections, int sockopts) } for (addrinfo* rp = result; rp; rp = rp->ai_next) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); _Socket = ::WSASocketA(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (_Socket == INVALID_SOCKET) continue; Loading Loading @@ -228,7 +228,7 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } if (_Socket == INVALID_SOCKET) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); _Socket = ::WSASocketA(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_OVERLAPPED); if (_Socket == INVALID_SOCKET) continue; } Loading Loading @@ -296,6 +296,9 @@ void tcp::setBlock() { } void tcp::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { if (!csock) { csock = std::make_unique<tcp>(); } // Create accept socket SOCKET acceptSock = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED); if (acceptSock == INVALID_SOCKET) { Loading @@ -304,6 +307,10 @@ void tcp::accept(LPFN_ACCEPTEX lpfnAcceptEx, std::unique_ptr<socket>& csock) { throw e; } // IOCP sockets should be non-blocking to prevent accidental blocking in recv/send u_long mode = 1; ::ioctlsocket(acceptSock, FIONBIO, &mode); if (!csock->_ReadBuffer) csock->_ReadBuffer.reset(new buffer); Loading