From d9318a37ec09fe0b002815a7e48710e530620ae2 Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Wed, 12 Jul 2023 16:10:05 -0400 Subject: [PATCH] net: split ConnectToSocket() from ConnectDirectly() for unix sockets --- src/netbase.cpp | 148 +++++++++++++++++++++++++++++++----------------- src/netbase.h | 2 + 2 files changed, 99 insertions(+), 51 deletions(-) diff --git a/src/netbase.cpp b/src/netbase.cpp index c8735bd43de..6af5d10ab1f 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -535,6 +535,61 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } +static bool ConnectToSocket(const Sock& sock, struct sockaddr* sockaddr, socklen_t len, const std::string& dest_str, bool manual_connection) +{ + // Connect to `sockaddr` using `sock`. + if (sock.Connect(sockaddr, len) == SOCKET_ERROR) { + int nErr = WSAGetLastError(); + // WSAEINVAL is here because some legacy version of winsock uses it + if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) + { + // Connection didn't actually fail, but is being established + // asynchronously. Thus, use async I/O api (select/poll) + // synchronously to check for successful connection with a timeout. + const Sock::Event requested = Sock::RECV | Sock::SEND; + Sock::Event occurred; + if (!sock.Wait(std::chrono::milliseconds{nConnectTimeout}, requested, &occurred)) { + LogPrintf("wait for connect to %s failed: %s\n", + dest_str, + NetworkErrorString(WSAGetLastError())); + return false; + } else if (occurred == 0) { + LogPrint(BCLog::NET, "connection attempt to %s timed out\n", dest_str); + return false; + } + + // Even if the wait was successful, the connect might not + // have been successful. The reason for this failure is hidden away + // in the SO_ERROR for the socket in modern systems. We read it into + // sockerr here. + int sockerr; + socklen_t sockerr_len = sizeof(sockerr); + if (sock.GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) == + SOCKET_ERROR) { + LogPrintf("getsockopt() for %s failed: %s\n", dest_str, NetworkErrorString(WSAGetLastError())); + return false; + } + if (sockerr != 0) { + LogConnectFailure(manual_connection, + "connect() to %s failed after wait: %s", + dest_str, + NetworkErrorString(sockerr)); + return false; + } + } +#ifdef WIN32 + else if (WSAGetLastError() != WSAEISCONN) +#else + else +#endif + { + LogConnectFailure(manual_connection, "connect() to %s failed: %s", dest_str, NetworkErrorString(WSAGetLastError())); + return false; + } + } + return true; +} + std::unique_ptr ConnectDirectly(const CService& dest, bool manual_connection) { auto sock = CreateSock(dest.GetSAFamily()); @@ -547,63 +602,54 @@ std::unique_ptr ConnectDirectly(const CService& dest, bool manual_connecti struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!dest.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - LogPrintf("Cannot connect to %s: unsupported network\n", dest.ToStringAddrPort()); + LogPrintf("Cannot get sockaddr for %s: unsupported network\n", dest.ToStringAddrPort()); return {}; } - // Connect to the dest service on the hSocket socket. - if (sock->Connect(reinterpret_cast(&sockaddr), len) == SOCKET_ERROR) { - int nErr = WSAGetLastError(); - // WSAEINVAL is here because some legacy version of winsock uses it - if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) - { - // Connection didn't actually fail, but is being established - // asynchronously. Thus, use async I/O api (select/poll) - // synchronously to check for successful connection with a timeout. - const Sock::Event requested = Sock::RECV | Sock::SEND; - Sock::Event occurred; - if (!sock->Wait(std::chrono::milliseconds{nConnectTimeout}, requested, &occurred)) { - LogPrintf("wait for connect to %s failed: %s\n", - dest.ToStringAddrPort(), - NetworkErrorString(WSAGetLastError())); - return {}; - } else if (occurred == 0) { - LogPrint(BCLog::NET, "connection attempt to %s timed out\n", dest.ToStringAddrPort()); - return {}; - } - - // Even if the wait was successful, the connect might not - // have been successful. The reason for this failure is hidden away - // in the SO_ERROR for the socket in modern systems. We read it into - // sockerr here. - int sockerr; - socklen_t sockerr_len = sizeof(sockerr); - if (sock->GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) == - SOCKET_ERROR) { - LogPrintf("getsockopt() for %s failed: %s\n", dest.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); - return {}; - } - if (sockerr != 0) { - LogConnectFailure(manual_connection, - "connect() to %s failed after wait: %s", - dest.ToStringAddrPort(), - NetworkErrorString(sockerr)); - return {}; - } - } -#ifdef WIN32 - else if (WSAGetLastError() != WSAEISCONN) -#else - else -#endif - { - LogConnectFailure(manual_connection, "connect() to %s failed: %s", dest.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); - return {}; - } + if (!ConnectToSocket(*sock, (struct sockaddr*)&sockaddr, len, dest.ToStringAddrPort(), manual_connection)) { + LogPrintf("Cannot connect to socket for %s\n", dest.ToStringAddrPort()); + return {}; } + return sock; } +std::unique_ptr Proxy::Connect() const +{ + if (!IsValid()) { + LogPrintf("Cannot connect to invalid Proxy\n"); + return {}; + } + + if (!m_is_unix_socket) return ConnectDirectly(proxy, /*manual_connection=*/true); + +#if HAVE_SOCKADDR_UN + auto sock = CreateSock(AF_UNIX); + if (!sock) { + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Cannot create a socket for connecting to %s\n", m_unix_socket_path); + return {}; + } + + const std::string path{m_unix_socket_path.substr(ADDR_PREFIX_UNIX.length())}; + + struct sockaddr_un addrun; + memset(&addrun, 0, sizeof(addrun)); + addrun.sun_family = AF_UNIX; + // leave the last char in addrun.sun_path[] to be always '\0' + memcpy(addrun.sun_path, path.c_str(), std::min(sizeof(addrun.sun_path) - 1, path.length())); + socklen_t len = sizeof(addrun); + + if(!ConnectToSocket(*sock, (struct sockaddr*)&addrun, len, path, /*manual_connection=*/true)) { + LogPrintf("Cannot connect to socket for %s\n", path); + return {}; + } + + return sock; +#else + return {}; +#endif +} + bool SetProxy(enum Network net, const Proxy &addrProxy) { assert(net >= 0 && net < NET_MAX); if (!addrProxy.IsValid()) @@ -658,7 +704,7 @@ std::unique_ptr ConnectThroughProxy(const Proxy& proxy, bool& proxy_connection_failed) { // first connect to proxy server - auto sock = ConnectDirectly(proxy.proxy, /*manual_connection=*/true); + auto sock = proxy.Connect(); if (!sock) { proxy_connection_failed = true; return {}; diff --git a/src/netbase.h b/src/netbase.h index 0cefddafead..321c288f67b 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -84,6 +84,8 @@ public: if (m_is_unix_socket) return m_unix_socket_path; return proxy.ToStringAddrPort(); } + + std::unique_ptr Connect() const; }; /** Credentials for proxy authentication */