Skip to content

Commit

Permalink
Merge #2172: [Net] Improve and document SOCKS code
Browse files Browse the repository at this point in the history
31b0fbd net: Improve and document SOCKS code (Wladimir J. van der Laan)

Pull request description:

  cherry-picked from bitcoin#11397.

  > Make the SOCKS code more consistent, and document the constants used.

ACKs for top commit:
  furszy:
    ACK 31b0fbd
  Fuzzbawls:
    ACK 31b0fbd

Tree-SHA512: 5b899667f103568bd48728789d5d3b688f10c13466e45e34ecaf6ca0651f27d7be880ddae80bd206af1fd3b01ca963c016260ec1a26cb89107c52870b8dd3fda
  • Loading branch information
random-zebra committed Feb 1, 2021
2 parents 70ac797 + 31b0fbd commit 3c60768
Showing 1 changed file with 101 additions and 52 deletions.
153 changes: 101 additions & 52 deletions src/netbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,48 @@ struct timeval MillisToTimeval(int64_t nTimeout)
return timeout;
}

/** SOCKS version */
enum SOCKSVersion: uint8_t {
SOCKS4 = 0x04,
SOCKS5 = 0x05
};

/** Values defined for METHOD in RFC1928 */
enum SOCKS5Method: uint8_t {
NOAUTH = 0x00, //! No authentication required
GSSAPI = 0x01, //! GSSAPI
USER_PASS = 0x02, //! Username/password
NO_ACCEPTABLE = 0xff, //! No acceptable methods
};

/** Values defined for CMD in RFC1928 */
enum SOCKS5Command: uint8_t {
CONNECT = 0x01,
BIND = 0x02,
UDP_ASSOCIATE = 0x03
};

/** Values defined for REP in RFC1928 */
enum SOCKS5Reply: uint8_t {
SUCCEEDED = 0x00, //! Succeeded
GENFAILURE = 0x01, //! General failure
NOTALLOWED = 0x02, //! Connection not allowed by ruleset
NETUNREACHABLE = 0x03, //! Network unreachable
HOSTUNREACHABLE = 0x04, //! Network unreachable
CONNREFUSED = 0x05, //! Connection refused
TTLEXPIRED = 0x06, //! TTL expired
CMDUNSUPPORTED = 0x07, //! Command not supported
ATYPEUNSUPPORTED = 0x08, //! Address type not supported
};

/** Values defined for ATYPE in RFC1928 */
enum SOCKS5Atyp: uint8_t {
IPV4 = 0x01,
DOMAINNAME = 0x03,
IPV6 = 0x04,
};

/** Status codes that can be returned by InterruptibleRecv */
enum class IntrRecvError {
OK,
Timeout,
Expand All @@ -217,15 +259,15 @@ enum class IntrRecvError {
*
* @note This function requires that hSocket is in non-blocking mode.
*/
static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, SOCKET& hSocket)
static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket)
{
int64_t curTime = GetTimeMillis();
int64_t endTime = curTime + timeout;
// Maximum time to wait in one select call. It will take up until this time (in millis)
// to break off in case of an interruption.
const int64_t maxWait = 1000;
while (len > 0 && curTime < endTime) {
ssize_t ret = recv(hSocket, data, len, 0); // Optimistically try the recv first
ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first
if (ret > 0) {
len -= ret;
data += ret;
Expand Down Expand Up @@ -256,24 +298,35 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, SOCK
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
}

/** Credentials for proxy authentication */
struct ProxyCredentials
{
std::string username;
std::string password;
};

std::string static Socks5ErrorString(int err)
/** Convert SOCKS5 reply to a an error message */
static std::string Socks5ErrorString(uint8_t err)
{
switch(err) {
case 0x01: return "general failure";
case 0x02: return "connection not allowed";
case 0x03: return "network unreachable";
case 0x04: return "host unreachable";
case 0x05: return "connection refused";
case 0x06: return "TTL expired";
case 0x07: return "protocol error";
case 0x08: return "address type not supported";
default: return "unknown";
case SOCKS5Reply::GENFAILURE:
return "general failure";
case SOCKS5Reply::NOTALLOWED:
return "connection not allowed";
case SOCKS5Reply::NETUNREACHABLE:
return "network unreachable";
case SOCKS5Reply::HOSTUNREACHABLE:
return "host unreachable";
case SOCKS5Reply::CONNREFUSED:
return "connection refused";
case SOCKS5Reply::TTLEXPIRED:
return "TTL expired";
case SOCKS5Reply::CMDUNSUPPORTED:
return "protocol error";
case SOCKS5Reply::ATYPEUNSUPPORTED:
return "address type not supported";
default:
return "unknown";
}
}

Expand All @@ -288,34 +341,34 @@ bool static Socks5(std::string strDest, int port, const ProxyCredentials *auth,
}
// Accepted authentication methods
std::vector<uint8_t> vSocks5Init;
vSocks5Init.push_back(0x05);
vSocks5Init.push_back(SOCKSVersion::SOCKS5);
if (auth) {
vSocks5Init.push_back(0x02); // # METHODS
vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED
vSocks5Init.push_back(0x02); // X'02' USERNAME/PASSWORD (RFC1929)
vSocks5Init.push_back(0x02); // Number of methods
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
} else {
vSocks5Init.push_back(0x01); // # METHODS
vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED
vSocks5Init.push_back(0x01); // Number of methods
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
}
ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
if (ret != (ssize_t)vSocks5Init.size()) {
CloseSocket(hSocket);
return error("Error sending to proxy");
}
char pchRet1[2];
uint8_t pchRet1[2];
if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
return false;
}
if (pchRet1[0] != 0x05) {
if (pchRet1[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket);
return error("Proxy failed to initialize");
}
if (pchRet1[1] == 0x02 && auth) {
if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
// Perform username/password authentication (as described in RFC1929)
std::vector<uint8_t> vAuth;
vAuth.push_back(0x01);
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");
vAuth.push_back(auth->username.size());
Expand All @@ -328,7 +381,7 @@ bool static Socks5(std::string strDest, int port, const ProxyCredentials *auth,
return error("Error sending authentication to proxy");
}
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
char pchRetA[2];
uint8_t pchRetA[2];
if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
return error("Error reading proxy authentication response");
Expand All @@ -337,17 +390,17 @@ bool static Socks5(std::string strDest, int port, const ProxyCredentials *auth,
CloseSocket(hSocket);
return error("Proxy authentication unsuccesful");
}
} else if (pchRet1[1] == 0x00) {
} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
// Perform no authentication
} else {
CloseSocket(hSocket);
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
}
std::vector<uint8_t> vSocks5;
vSocks5.push_back(0x05); // VER protocol version
vSocks5.push_back(0x01); // CMD CONNECT
vSocks5.push_back(0x00); // RSV Reserved
vSocks5.push_back(0x03); // ATYP DOMAINNAME
vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
vSocks5.push_back(0x00); // RSV Reserved must be 0
vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
vSocks5.push_back((port >> 8) & 0xFF);
Expand All @@ -357,7 +410,7 @@ bool static Socks5(std::string strDest, int port, const ProxyCredentials *auth,
CloseSocket(hSocket);
return error("Error sending to proxy");
}
char pchRet2[4];
uint8_t pchRet2[4];
if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
if (recvr == IntrRecvError::Timeout) {
Expand All @@ -369,41 +422,37 @@ bool static Socks5(std::string strDest, int port, const ProxyCredentials *auth,
return error("Error while reading proxy response");
}
}
if (pchRet2[0] != 0x05) {
if (pchRet2[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket);
return error("Proxy failed to accept request");
}
if (pchRet2[1] != 0x00) {
if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
// Failures to connect to a peer that are not proxy errors
CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
return false;
}
if (pchRet2[2] != 0x00) {
if (pchRet2[2] != 0x00) { // Reserved field must be 0
CloseSocket(hSocket);
return error("Error: malformed proxy response");
}
char pchRet3[256];
switch (pchRet2[3]) {
case 0x01:
recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket);
break;
case 0x04:
recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket);
break;
case 0x03: {
recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket);
if (recvr != IntrRecvError::OK) {
CloseSocket(hSocket);
return error("Error reading from proxy");
uint8_t pchRet3[256];
switch (pchRet2[3])
{
case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break;
case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break;
case SOCKS5Atyp::DOMAINNAME:
{
recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket);
if (recvr != IntrRecvError::OK) {
CloseSocket(hSocket);
return error("Error reading from proxy");
}
int nRecv = pchRet3[0];
recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket);
break;
}
int nRecv = pchRet3[0];
recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket);
break;
}
default:
CloseSocket(hSocket);
return error("Error: malformed proxy response");
default: CloseSocket(hSocket); return error("Error: malformed proxy response");
}
if (recvr != IntrRecvError::OK) {
CloseSocket(hSocket);
Expand Down

0 comments on commit 3c60768

Please sign in to comment.