Commit a2e5648d authored by jan.koester's avatar jan.koester
Browse files

test

parent 3499d373
Loading
Loading
Loading
Loading
+149 −35
Original line number Diff line number Diff line
@@ -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>
@@ -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() {
@@ -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 (...) {
@@ -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
@@ -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);
@@ -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
        // -------------------------------------------------------
@@ -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]{};