diff options
Diffstat (limited to 'src/netbase.cpp')
-rw-r--r-- | src/netbase.cpp | 215 |
1 files changed, 156 insertions, 59 deletions
diff --git a/src/netbase.cpp b/src/netbase.cpp index 9fbd9f7dea..e231766487 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <config/bitcoin-config.h> // IWYU pragma: keep + #include <netbase.h> #include <compat/compat.h> @@ -21,6 +23,10 @@ #include <limits> #include <memory> +#if HAVE_SOCKADDR_UN +#include <sys/un.h> +#endif + // Settings static GlobalMutex g_proxyinfo_mutex; static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); @@ -208,6 +214,24 @@ CService LookupNumeric(const std::string& name, uint16_t portDefault, DNSLookupF return Lookup(name, portDefault, /*fAllowLookup=*/false, dns_lookup_function).value_or(CService{}); } +bool IsUnixSocketPath(const std::string& name) +{ +#if HAVE_SOCKADDR_UN + if (name.find(ADDR_PREFIX_UNIX) != 0) return false; + + // Split off "unix:" prefix + std::string str{name.substr(ADDR_PREFIX_UNIX.length())}; + + // Path size limit is platform-dependent + // see https://manpages.ubuntu.com/manpages/xenial/en/man7/unix.7.html + if (str.size() + 1 > sizeof(((sockaddr_un*)nullptr)->sun_path)) return false; + + return true; +#else + return false; +#endif +} + /** SOCKS version */ enum SOCKSVersion: uint8_t { SOCKS4 = 0x04, @@ -338,7 +362,8 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a IntrRecvError recvr; LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); if (strDest.size() > 255) { - return error("Hostname too long"); + LogError("Hostname too long\n"); + return false; } // Construct the version identifier/method selection message std::vector<uint8_t> vSocks5Init; @@ -358,14 +383,17 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a return false; } if (pchRet1[0] != SOCKSVersion::SOCKS5) { - return error("Proxy failed to initialize"); + LogError("Proxy failed to initialize\n"); + return false; } if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) { // Perform username/password authentication (as described in RFC1929) std::vector<uint8_t> vAuth; vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation - if (auth->username.size() > 255 || auth->password.size() > 255) - return error("Proxy username or password too long"); + if (auth->username.size() > 255 || auth->password.size() > 255) { + LogError("Proxy username or password too long\n"); + return false; + } vAuth.push_back(auth->username.size()); vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); vAuth.push_back(auth->password.size()); @@ -374,15 +402,18 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { - return error("Error reading proxy authentication response"); + LogError("Error reading proxy authentication response\n"); + return false; } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { - return error("Proxy authentication unsuccessful"); + LogError("Proxy authentication unsuccessful\n"); + return false; } } else if (pchRet1[1] == SOCKS5Method::NOAUTH) { // Perform no authentication } else { - return error("Proxy requested wrong authentication method %02x", pchRet1[1]); + LogError("Proxy requested wrong authentication method %02x\n", pchRet1[1]); + return false; } std::vector<uint8_t> vSocks5; vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version @@ -402,11 +433,13 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a * error message. */ return false; } else { - return error("Error while reading proxy response"); + LogError("Error while reading proxy response\n"); + return false; } } if (pchRet2[0] != SOCKSVersion::SOCKS5) { - return error("Proxy failed to accept request"); + LogError("Proxy failed to accept request\n"); + return false; } if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) { // Failures to connect to a peer that are not proxy errors @@ -414,7 +447,8 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a return false; } if (pchRet2[2] != 0x00) { // Reserved field must be 0 - return error("Error: malformed proxy response"); + LogError("Error: malformed proxy response\n"); + return false; } uint8_t pchRet3[256]; switch (pchRet2[3]) { @@ -423,39 +457,46 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a case SOCKS5Atyp::DOMAINNAME: { recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock); if (recvr != IntrRecvError::OK) { - return error("Error reading from proxy"); + LogError("Error reading from proxy\n"); + return false; } int nRecv = pchRet3[0]; recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock); break; } - default: return error("Error: malformed proxy response"); + default: { + LogError("Error: malformed proxy response\n"); + return false; + } } if (recvr != IntrRecvError::OK) { - return error("Error reading from proxy"); + LogError("Error reading from proxy\n"); + return false; } if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { - return error("Error reading from proxy"); + LogError("Error reading from proxy\n"); + return false; } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); return true; } catch (const std::runtime_error& e) { - return error("Error during SOCKS5 proxy handshake: %s", e.what()); + LogError("Error during SOCKS5 proxy handshake: %s\n", e.what()); + return false; } } -std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) +std::unique_ptr<Sock> CreateSockOS(sa_family_t address_family) { - // Create a sockaddr from the specified service. - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - if (!address_family.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToStringAddrPort()); - return nullptr; - } + // Not IPv4, IPv6 or UNIX + if (address_family == AF_UNSPEC) return nullptr; + + int protocol{IPPROTO_TCP}; +#if HAVE_SOCKADDR_UN + if (address_family == AF_UNIX) protocol = 0; +#endif - // Create a TCP socket in the address family of the specified service. - SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); + // Create a socket in the specified address family. + SOCKET hSocket = socket(address_family, SOCK_STREAM, protocol); if (hSocket == INVALID_SOCKET) { return nullptr; } @@ -479,21 +520,25 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) } #endif - // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. - const int on{1}; - if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { - LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n"); - } - // Set the non-blocking option on the socket. if (!sock->SetNonBlocking()) { LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError())); return nullptr; } + +#if HAVE_SOCKADDR_UN + if (address_family == AF_UNIX) return sock; +#endif + + // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. + const int on{1}; + if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { + LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n"); + } return sock; } -std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP; +std::function<std::unique_ptr<Sock>(const sa_family_t&)> CreateSock = CreateSockOS; template<typename... Args> static void LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args) { @@ -505,18 +550,10 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } -bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection) +static bool ConnectToSocket(const Sock& sock, struct sockaddr* sockaddr, socklen_t len, const std::string& dest_str, bool manual_connection) { - // Create a sockaddr from the specified service. - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToStringAddrPort()); - return false; - } - - // Connect to the addrConnect service on the hSocket socket. - if (sock.Connect(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) { + // 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) @@ -526,13 +563,13 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT // 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{nTimeout}, requested, &occurred)) { + if (!sock.Wait(std::chrono::milliseconds{nConnectTimeout}, requested, &occurred)) { LogPrintf("wait for connect to %s failed: %s\n", - addrConnect.ToStringAddrPort(), + dest_str, NetworkErrorString(WSAGetLastError())); return false; } else if (occurred == 0) { - LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToStringAddrPort()); + LogPrint(BCLog::NET, "connection attempt to %s timed out\n", dest_str); return false; } @@ -544,13 +581,13 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT 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", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); + 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", - addrConnect.ToStringAddrPort(), + dest_str, NetworkErrorString(sockerr)); return false; } @@ -561,13 +598,68 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT else #endif { - LogConnectFailure(manual_connection, "connect() to %s failed: %s", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); + LogConnectFailure(manual_connection, "connect() to %s failed: %s", dest_str, NetworkErrorString(WSAGetLastError())); return false; } } return true; } +std::unique_ptr<Sock> ConnectDirectly(const CService& dest, bool manual_connection) +{ + auto sock = CreateSock(dest.GetSAFamily()); + if (!sock) { + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "Cannot create a socket for connecting to %s\n", dest.ToStringAddrPort()); + return {}; + } + + // Create a sockaddr from the specified service. + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + if (!dest.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { + LogPrintf("Cannot get sockaddr for %s: unsupported network\n", dest.ToStringAddrPort()); + return {}; + } + + if (!ConnectToSocket(*sock, (struct sockaddr*)&sockaddr, len, dest.ToStringAddrPort(), manual_connection)) { + return {}; + } + + return sock; +} + +std::unique_ptr<Sock> Proxy::Connect() const +{ + if (!IsValid()) 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)) { + return {}; + } + + return sock; +#else + return {}; +#endif +} + bool SetProxy(enum Network net, const Proxy &addrProxy) { assert(net >= 0 && net < NET_MAX); if (!addrProxy.IsValid()) @@ -616,27 +708,32 @@ bool IsProxy(const CNetAddr &addr) { return false; } -bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) +std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy, + const std::string& dest, + uint16_t port, + bool& proxy_connection_failed) { // first connect to proxy server - if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) { - outProxyConnectionFailed = true; - return false; + auto sock = proxy.Connect(); + if (!sock) { + proxy_connection_failed = true; + return {}; } + // do socks negotiation - if (proxy.randomize_credentials) { + if (proxy.m_randomize_credentials) { ProxyCredentials random_auth; static std::atomic_int counter(0); random_auth.username = random_auth.password = strprintf("%i", counter++); - if (!Socks5(strDest, port, &random_auth, sock)) { - return false; + if (!Socks5(dest, port, &random_auth, *sock)) { + return {}; } } else { - if (!Socks5(strDest, port, nullptr, sock)) { - return false; + if (!Socks5(dest, port, nullptr, *sock)) { + return {}; } } - return true; + return sock; } CSubNet LookupSubNet(const std::string& subnet_str) |