Loading src/posix/tcp.cpp +149 −35 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <cstdio> #include <cstring> #include <cerrno> #include <chrono> #include <iostream> #include <unistd.h> #include <netdb.h> Loading @@ -44,6 +45,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MSG_NOSIGNAL 0 #endif namespace { // Per-address bound for the internal non-blocking connect()+wait used by // blocking-facing callers (tcp::connect(nonblock=false), tcp::connectTimeout). // Without this, a routable-but-unresponsive address (e.g. a blackholed IPv6 // route sorted before a working IPv4 one, RFC 6724) turns a raw blocking // connect() into a multi-minute stall (Linux tcp_syn_retries defaults to // ~127s) before the next resolved address is even tried. Mirrors the // timeout already used on the Windows side of this same call. constexpr int kConnectAttemptTimeoutMs = 10000; } namespace netplus { tcp::tcp() : socket() { Loading Loading @@ -317,9 +329,12 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } // ------------------------------------------------------- // 2) set nonblock BEFORE connect() if requested // 2) always connect non-blocking internally, even when the // caller asked for a blocking connect() -- a raw blocking // connect() has no per-address timeout, so one unresponsive // address stalls the whole call for the OS's own retry // timeout instead of falling through to the next address. // ------------------------------------------------------- if (nonblock) { try { setNonBlock(); } catch (...) { Loading @@ -327,13 +342,13 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) _Socket = -1; continue; } } // ------------------------------------------------------- // 3) connect() // ------------------------------------------------------- if (::connect(_Socket, rp->ai_addr, rp->ai_addrlen) == 0) { // ✅ connected immediately if (!nonblock) setBlock(); setAddrFromAI(rp); // Disable Nagle's algorithm for low-latency HTTP Loading @@ -347,7 +362,8 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) // ------------------------------------------------------- // 4) nonblocking in progress // ------------------------------------------------------- if (nonblock && (errno == EINPROGRESS || errno == EWOULDBLOCK)) { if (errno == EINPROGRESS || errno == EWOULDBLOCK) { if (nonblock) { setAddrFromAI(rp); ::freeaddrinfo(result); Loading @@ -357,6 +373,31 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) throw n; // caller waits EPOLLOUT/select } // Caller wanted a blocking connect(): wait out this one // address up to kConnectAttemptTimeoutMs, then either finish // up (restoring blocking mode) or fall through to the next // resolved address instead of hanging on this one. netplus::socketwait sw; if (sw.waitWrite(*this, kConnectAttemptTimeoutMs)) { int err = 0; socklen_t errlen = sizeof(err); if (::getsockopt(_Socket, SOL_SOCKET, SO_ERROR, &err, &errlen) == 0 && err == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } } ::close(_Socket); _Socket = -1; continue; } // ------------------------------------------------------- // 5) fatal error -> try next addrinfo // ------------------------------------------------------- Loading @@ -374,31 +415,104 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } void tcp::connectTimeout(const std::string& addr, int port, int timeout_ms) { // Not just a single connect(addr, port, true) + wait: that only ever // attempts the *first* address getaddrinfo() returns, so a single // unresponsive address (e.g. a blackholed IPv6 route sorted before a // working IPv4 one) burns the whole timeout_ms and fails outright, // even when a later address in the list would have connected fine. // Instead, walk the full address list ourselves, spending only what's // left of the overall budget on each one. NetException exception; addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; addrinfo* result = nullptr; char serv[32]; std::snprintf(serv, sizeof(serv), "%d", port); int gai = ::getaddrinfo(addr.c_str(), serv, &hints, &result); if (gai != 0) { exception[NetException::Error] << "tcp::connectTimeout: getaddrinfo failed: " << gai_strerror(gai); throw exception; } const auto start = std::chrono::steady_clock::now(); for (addrinfo* rp = result; rp; rp = rp->ai_next) { int remaining_ms = timeout_ms - (int)std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - start).count(); if (remaining_ms <= 0) break; if (_Socket >= 0) { sockaddr_storage tmp{}; socklen_t tmpLen = sizeof(tmp); if (::getsockname(_Socket, (sockaddr*)&tmp, &tmpLen) == 0) { if (tmp.ss_family != rp->ai_family) { ::close(_Socket); _Socket = -1; } } } if (_Socket < 0) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (_Socket < 0) continue; } try { connect(addr, port, true); return; // connected immediately } catch (NetException &e) { if (e.getErrorType() != NetException::Note) throw; setNonBlock(); } catch (...) { ::close(_Socket); _Socket = -1; continue; } netplus::socketwait sw; if (!sw.waitWrite(*this, timeout_ms)) { NetException ne; ne[NetException::Error] << "tcp::connectTimeout: timed out connecting to " << addr << ":" << port; throw ne; if (::connect(_Socket, rp->ai_addr, rp->ai_addrlen) == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } if (errno == EINPROGRESS || errno == EWOULDBLOCK) { netplus::socketwait sw; if (sw.waitWrite(*this, remaining_ms)) { int err = 0; socklen_t errlen = sizeof(err); if (::getsockopt(fd(), SOL_SOCKET, SO_ERROR, &err, &errlen) != 0 || err != 0) { NetException ne; ne[NetException::Error] << "tcp::connectTimeout: could not connect to " << addr << ":" << port << " (" << strerror(err) << ")"; throw ne; if (::getsockopt(_Socket, SOL_SOCKET, SO_ERROR, &err, &errlen) == 0 && err == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } } } ::close(_Socket); _Socket = -1; } ::freeaddrinfo(result); exception[NetException::Error] << "tcp::connectTimeout: could not connect to " << addr << ":" << port; throw exception; } void tcp::getAddress(std::string& addr) { char buf[INET6_ADDRSTRLEN]{}; Loading Loading
src/posix/tcp.cpp +149 −35 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <cstdio> #include <cstring> #include <cerrno> #include <chrono> #include <iostream> #include <unistd.h> #include <netdb.h> Loading @@ -44,6 +45,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MSG_NOSIGNAL 0 #endif namespace { // Per-address bound for the internal non-blocking connect()+wait used by // blocking-facing callers (tcp::connect(nonblock=false), tcp::connectTimeout). // Without this, a routable-but-unresponsive address (e.g. a blackholed IPv6 // route sorted before a working IPv4 one, RFC 6724) turns a raw blocking // connect() into a multi-minute stall (Linux tcp_syn_retries defaults to // ~127s) before the next resolved address is even tried. Mirrors the // timeout already used on the Windows side of this same call. constexpr int kConnectAttemptTimeoutMs = 10000; } namespace netplus { tcp::tcp() : socket() { Loading Loading @@ -317,9 +329,12 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } // ------------------------------------------------------- // 2) set nonblock BEFORE connect() if requested // 2) always connect non-blocking internally, even when the // caller asked for a blocking connect() -- a raw blocking // connect() has no per-address timeout, so one unresponsive // address stalls the whole call for the OS's own retry // timeout instead of falling through to the next address. // ------------------------------------------------------- if (nonblock) { try { setNonBlock(); } catch (...) { Loading @@ -327,13 +342,13 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) _Socket = -1; continue; } } // ------------------------------------------------------- // 3) connect() // ------------------------------------------------------- if (::connect(_Socket, rp->ai_addr, rp->ai_addrlen) == 0) { // ✅ connected immediately if (!nonblock) setBlock(); setAddrFromAI(rp); // Disable Nagle's algorithm for low-latency HTTP Loading @@ -347,7 +362,8 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) // ------------------------------------------------------- // 4) nonblocking in progress // ------------------------------------------------------- if (nonblock && (errno == EINPROGRESS || errno == EWOULDBLOCK)) { if (errno == EINPROGRESS || errno == EWOULDBLOCK) { if (nonblock) { setAddrFromAI(rp); ::freeaddrinfo(result); Loading @@ -357,6 +373,31 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) throw n; // caller waits EPOLLOUT/select } // Caller wanted a blocking connect(): wait out this one // address up to kConnectAttemptTimeoutMs, then either finish // up (restoring blocking mode) or fall through to the next // resolved address instead of hanging on this one. netplus::socketwait sw; if (sw.waitWrite(*this, kConnectAttemptTimeoutMs)) { int err = 0; socklen_t errlen = sizeof(err); if (::getsockopt(_Socket, SOL_SOCKET, SO_ERROR, &err, &errlen) == 0 && err == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } } ::close(_Socket); _Socket = -1; continue; } // ------------------------------------------------------- // 5) fatal error -> try next addrinfo // ------------------------------------------------------- Loading @@ -374,31 +415,104 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) } void tcp::connectTimeout(const std::string& addr, int port, int timeout_ms) { // Not just a single connect(addr, port, true) + wait: that only ever // attempts the *first* address getaddrinfo() returns, so a single // unresponsive address (e.g. a blackholed IPv6 route sorted before a // working IPv4 one) burns the whole timeout_ms and fails outright, // even when a later address in the list would have connected fine. // Instead, walk the full address list ourselves, spending only what's // left of the overall budget on each one. NetException exception; addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; addrinfo* result = nullptr; char serv[32]; std::snprintf(serv, sizeof(serv), "%d", port); int gai = ::getaddrinfo(addr.c_str(), serv, &hints, &result); if (gai != 0) { exception[NetException::Error] << "tcp::connectTimeout: getaddrinfo failed: " << gai_strerror(gai); throw exception; } const auto start = std::chrono::steady_clock::now(); for (addrinfo* rp = result; rp; rp = rp->ai_next) { int remaining_ms = timeout_ms - (int)std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - start).count(); if (remaining_ms <= 0) break; if (_Socket >= 0) { sockaddr_storage tmp{}; socklen_t tmpLen = sizeof(tmp); if (::getsockname(_Socket, (sockaddr*)&tmp, &tmpLen) == 0) { if (tmp.ss_family != rp->ai_family) { ::close(_Socket); _Socket = -1; } } } if (_Socket < 0) { _Socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (_Socket < 0) continue; } try { connect(addr, port, true); return; // connected immediately } catch (NetException &e) { if (e.getErrorType() != NetException::Note) throw; setNonBlock(); } catch (...) { ::close(_Socket); _Socket = -1; continue; } netplus::socketwait sw; if (!sw.waitWrite(*this, timeout_ms)) { NetException ne; ne[NetException::Error] << "tcp::connectTimeout: timed out connecting to " << addr << ":" << port; throw ne; if (::connect(_Socket, rp->ai_addr, rp->ai_addrlen) == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } if (errno == EINPROGRESS || errno == EWOULDBLOCK) { netplus::socketwait sw; if (sw.waitWrite(*this, remaining_ms)) { int err = 0; socklen_t errlen = sizeof(err); if (::getsockopt(fd(), SOL_SOCKET, SO_ERROR, &err, &errlen) != 0 || err != 0) { NetException ne; ne[NetException::Error] << "tcp::connectTimeout: could not connect to " << addr << ":" << port << " (" << strerror(err) << ")"; throw ne; if (::getsockopt(_Socket, SOL_SOCKET, SO_ERROR, &err, &errlen) == 0 && err == 0) { setBlock(); setAddrFromAI(rp); int one = 1; ::setsockopt(_Socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); ::freeaddrinfo(result); return; } } } ::close(_Socket); _Socket = -1; } ::freeaddrinfo(result); exception[NetException::Error] << "tcp::connectTimeout: could not connect to " << addr << ":" << port; throw exception; } void tcp::getAddress(std::string& addr) { char buf[INET6_ADDRSTRLEN]{}; Loading