Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251enum SOCKS5Method: uint8_t {252NOAUTH = 0x00, //!< No authentication required253GSSAPI = 0x01, //!< GSSAPI254USER_PASS = 0x02, //!< Username/password255NO_ACCEPTABLE = 0xff, //!< No acceptable methods256};258/** Values defined for CMD in RFC1928 */259enum SOCKS5Command: uint8_t {260CONNECT = 0x01,261BIND = 0x02,262UDP_ASSOCIATE = 0x03263};265/** Values defined for REP in RFC1928 and https://spec.torproject.org/socks-extensions.html */266enum SOCKS5Reply: uint8_t {267SUCCEEDED = 0x00, //!< RFC1928: Succeeded268GENFAILURE = 0x01, //!< RFC1928: General failure269NOTALLOWED = 0x02, //!< RFC1928: Connection not allowed by ruleset270NETUNREACHABLE = 0x03, //!< RFC1928: Network unreachable271HOSTUNREACHABLE = 0x04, //!< RFC1928: Network unreachable272CONNREFUSED = 0x05, //!< RFC1928: Connection refused273TTLEXPIRED = 0x06, //!< RFC1928: TTL expired274CMDUNSUPPORTED = 0x07, //!< RFC1928: Command not supported275ATYPEUNSUPPORTED = 0x08, //!< RFC1928: Address type not supported276TOR_HS_DESC_NOT_FOUND = 0xf0, //!< Tor: Onion service descriptor can not be found277TOR_HS_DESC_INVALID = 0xf1, //!< Tor: Onion service descriptor is invalid278TOR_HS_INTRO_FAILED = 0xf2, //!< Tor: Onion service introduction failed279TOR_HS_REND_FAILED = 0xf3, //!< Tor: Onion service rendezvous failed280TOR_HS_MISSING_CLIENT_AUTH = 0xf4, //!< Tor: Onion service missing client authorization281TOR_HS_WRONG_CLIENT_AUTH = 0xf5, //!< Tor: Onion service wrong client authorization282TOR_HS_BAD_ADDRESS = 0xf6, //!< Tor: Onion service invalid address283TOR_HS_INTRO_TIMEOUT = 0xf7, //!< Tor: Onion service introduction timed out284};286/** Values defined for ATYPE in RFC1928 */287enum SOCKS5Atyp: uint8_t {288IPV4 = 0x01,289DOMAINNAME = 0x03,290IPV6 = 0x04,291};293/** Status codes that can be returned by InterruptibleRecv */294enum class IntrRecvError {295OK,296Timeout,297Disconnected,298NetworkError,299Interrupted300};302/**303* Try to read a specified number of bytes from a socket. Please read the "see304* also" section for more detail.305*306* @param data The buffer where the read bytes should be stored.307* @param len The number of bytes to read into the specified buffer.308* @param timeout The total timeout for this read.309* @param sock The socket (has to be in non-blocking mode) from which to read bytes.310*311* @returns An IntrRecvError indicating the resulting status of this read.312* IntrRecvError::OK only if all of the specified number of bytes were313* read.314*315* @see This function can be interrupted by calling g_socks5_interrupt().316* Sockets can be made non-blocking with Sock::SetNonBlocking().317*/318static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::milliseconds timeout, const Sock& sock)319{320auto curTime{Now<SteadyMilliseconds>()};321const auto endTime{curTime + timeout};322while (len > 0 && curTime < endTime) {323ssize_t ret = sock.Recv(data, len, 0); // Optimistically try the recv first324if (ret > 0) {325len -= ret;326data += ret;327} else if (ret == 0) { // Unexpected disconnection328return IntrRecvError::Disconnected;329} else { // Other error or blocking330int nErr = WSAGetLastError();331if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) {332// Only wait at most MAX_WAIT_FOR_IO at a time, unless333// we're approaching the end of the specified total timeout334const auto remaining = std::chrono::milliseconds{endTime - curTime};335const auto timeout = std::min(remaining, std::chrono::milliseconds{MAX_WAIT_FOR_IO});336if (!sock.Wait(timeout, Sock::RecvEvent)) {337return IntrRecvError::NetworkError;338}339} else {340return IntrRecvError::NetworkError;341}342}343if (g_socks5_interrupt) {344return IntrRecvError::Interrupted;345}346curTime = Now<SteadyMilliseconds>();347}348return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;349}351/** Convert SOCKS5 reply to an error message */352static std::string Socks5ErrorString(uint8_t err)353{354switch(err) {355case SOCKS5Reply::GENFAILURE:356return "general failure";357case SOCKS5Reply::NOTALLOWED:358return "connection not allowed";359case SOCKS5Reply::NETUNREACHABLE:360return "network unreachable";361case SOCKS5Reply::HOSTUNREACHABLE:362return "host unreachable";363case SOCKS5Reply::CONNREFUSED:364return "connection refused";365case SOCKS5Reply::TTLEXPIRED:366return "TTL expired";367case SOCKS5Reply::CMDUNSUPPORTED:368return "protocol error";369case SOCKS5Reply::ATYPEUNSUPPORTED:370return "address type not supported";371case SOCKS5Reply::TOR_HS_DESC_NOT_FOUND:372return "onion service descriptor can not be found";373case SOCKS5Reply::TOR_HS_DESC_INVALID:374return "onion service descriptor is invalid";375case SOCKS5Reply::TOR_HS_INTRO_FAILED:376return "onion service introduction failed";377case SOCKS5Reply::TOR_HS_REND_FAILED:378return "onion service rendezvous failed";379case SOCKS5Reply::TOR_HS_MISSING_CLIENT_AUTH:380return "onion service missing client authorization";381case SOCKS5Reply::TOR_HS_WRONG_CLIENT_AUTH:382return "onion service wrong client authorization";383case SOCKS5Reply::TOR_HS_BAD_ADDRESS:384return "onion service invalid address";385case SOCKS5Reply::TOR_HS_INTRO_TIMEOUT:386return "onion service introduction timed out";387default:388return strprintf("unknown (0x%02x)", err);389}390}392bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock)393{394try {395IntrRecvError recvr;396LogDebug(BCLog::NET, "SOCKS5 connecting %s\n", strDest);397if (strDest.size() > 255) {398LogError("Hostname too long\n");399return false;400}401// Construct the version identifier/method selection message402std::vector<uint8_t> vSocks5Init;403vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol404if (auth) {405vSocks5Init.push_back(0x02); // 2 method identifiers follow...406vSocks5Init.push_back(SOCKS5Method::NOAUTH);407vSocks5Init.push_back(SOCKS5Method::USER_PASS);408} else {409vSocks5Init.push_back(0x01); // 1 method identifier follows...410vSocks5Init.push_back(SOCKS5Method::NOAUTH);411}412sock.SendComplete(vSocks5Init, g_socks5_recv_timeout, g_socks5_interrupt);413uint8_t pchRet1[2];414if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {415LogInfo("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);416return false;417}418if (pchRet1[0] != SOCKSVersion::SOCKS5) {419LogError("Proxy failed to initialize\n");420return false;421}422if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {423// Perform username/password authentication (as described in RFC1929)424std::vector<uint8_t> vAuth;425vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation426if (auth->username.size() > 255 || auth->password.size() > 255) {427LogError("Proxy username or password too long\n");428return false;429}430vAuth.push_back(auth->username.size());431vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());432vAuth.push_back(auth->password.size());433vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());434LogDebug(BCLog::PROXY, "SOCKS5 sending username/password authentication\n");435sock.SendComplete(vAuth, g_socks5_recv_timeout, g_socks5_interrupt);436uint8_t pchRetA[2];437if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {438LogError("Error reading proxy authentication response\n");439return false;440}441if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {442LogError("Proxy authentication unsuccessful\n");443return false;444}445} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {446// Perform no authentication447} else {448LogError("Proxy requested wrong authentication method %02x\n", pchRet1[1]);449return false;450}451std::vector<uint8_t> vSocks5;452vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version453vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT454vSocks5.push_back(0x00); // RSV Reserved must be 0455vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME456vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function457vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());458vSocks5.push_back((port >> 8) & 0xFF);459vSocks5.push_back((port >> 0) & 0xFF);460sock.SendComplete(vSocks5, g_socks5_recv_timeout, g_socks5_interrupt);461uint8_t pchRet2[4];462if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {463if (recvr == IntrRecvError::Timeout) {464/* If a timeout happens here, this effectively means we timed out while connecting465* to the remote node. This is very common for Tor, so do not print an466* error message. */467return false;468} else {469LogError("Error while reading proxy response\n");470return false;471}472}473if (pchRet2[0] != SOCKSVersion::SOCKS5) {474LogError("Proxy failed to accept request\n");475return false;476}477if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {478// Failures to connect to a peer that are not proxy errors479LogDebug(BCLog::NET,480"Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));481return false;482}483if (pchRet2[2] != 0x00) { // Reserved field must be 0484LogError("Error: malformed proxy response\n");485return false;486}487uint8_t pchRet3[256];488switch (pchRet2[3]) {489case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break;490case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break;491case SOCKS5Atyp::DOMAINNAME: {492recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock);493if (recvr != IntrRecvError::OK) {494LogError("Error reading from proxy\n");495return false;496}497int nRecv = pchRet3[0];498recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock);499break;500}