net: Improve and document SOCKS code

Make the SOCKS code more consistent, and document the constants used.
This commit is contained in:
Wladimir J. van der Laan 2017-09-23 10:36:23 +02:00
parent 10a20bf770
commit 22f816ef4d

View file

@ -184,6 +184,48 @@ struct timeval MillisToTimeval(int64_t nTimeout)
return timeout; 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 { enum class IntrRecvError {
OK, OK,
Timeout, Timeout,
@ -203,7 +245,7 @@ enum class IntrRecvError {
* *
* @note This function requires that hSocket is in non-blocking mode. * @note This function requires that hSocket is in non-blocking mode.
*/ */
static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, const SOCKET& hSocket) static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket)
{ {
int64_t curTime = GetTimeMillis(); int64_t curTime = GetTimeMillis();
int64_t endTime = curTime + timeout; int64_t endTime = curTime + timeout;
@ -211,7 +253,7 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
// to break off in case of an interruption. // to break off in case of an interruption.
const int64_t maxWait = 1000; const int64_t maxWait = 1000;
while (len > 0 && curTime < endTime) { 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) { if (ret > 0) {
len -= ret; len -= ret;
data += ret; data += ret;
@ -242,24 +284,35 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
} }
/** Credentials for proxy authentication */
struct ProxyCredentials struct ProxyCredentials
{ {
std::string username; std::string username;
std::string password; std::string password;
}; };
std::string Socks5ErrorString(int err) /** Convert SOCKS5 reply to a an error message */
std::string Socks5ErrorString(uint8_t err)
{ {
switch(err) { switch(err) {
case 0x01: return "general failure"; case SOCKS5Reply::GENFAILURE:
case 0x02: return "connection not allowed"; return "general failure";
case 0x03: return "network unreachable"; case SOCKS5Reply::NOTALLOWED:
case 0x04: return "host unreachable"; return "connection not allowed";
case 0x05: return "connection refused"; case SOCKS5Reply::NETUNREACHABLE:
case 0x06: return "TTL expired"; return "network unreachable";
case 0x07: return "protocol error"; case SOCKS5Reply::HOSTUNREACHABLE:
case 0x08: return "address type not supported"; return "host unreachable";
default: return "unknown"; 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";
} }
} }
@ -274,34 +327,34 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
} }
// Accepted authentication methods // Accepted authentication methods
std::vector<uint8_t> vSocks5Init; std::vector<uint8_t> vSocks5Init;
vSocks5Init.push_back(0x05); vSocks5Init.push_back(SOCKSVersion::SOCKS5);
if (auth) { if (auth) {
vSocks5Init.push_back(0x02); // # METHODS vSocks5Init.push_back(0x02); // Number of methods
vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED vSocks5Init.push_back(SOCKS5Method::NOAUTH);
vSocks5Init.push_back(0x02); // X'02' USERNAME/PASSWORD (RFC1929) vSocks5Init.push_back(SOCKS5Method::USER_PASS);
} else { } else {
vSocks5Init.push_back(0x01); // # METHODS vSocks5Init.push_back(0x01); // Number of methods
vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED vSocks5Init.push_back(SOCKS5Method::NOAUTH);
} }
ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
if (ret != (ssize_t)vSocks5Init.size()) { if (ret != (ssize_t)vSocks5Init.size()) {
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Error sending to proxy"); return error("Error sending to proxy");
} }
char pchRet1[2]; uint8_t pchRet1[2];
if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket); CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port); LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
return false; return false;
} }
if (pchRet1[0] != 0x05) { if (pchRet1[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Proxy failed to initialize"); 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) // Perform username/password authentication (as described in RFC1929)
std::vector<uint8_t> vAuth; 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) if (auth->username.size() > 255 || auth->password.size() > 255)
return error("Proxy username or password too long"); return error("Proxy username or password too long");
vAuth.push_back(auth->username.size()); vAuth.push_back(auth->username.size());
@ -314,7 +367,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return error("Error sending authentication to proxy"); return error("Error sending authentication to proxy");
} }
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); 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) { if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Error reading proxy authentication response"); return error("Error reading proxy authentication response");
@ -323,17 +376,17 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Proxy authentication unsuccessful"); return error("Proxy authentication unsuccessful");
} }
} else if (pchRet1[1] == 0x00) { } else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
// Perform no authentication // Perform no authentication
} else { } else {
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Proxy requested wrong authentication method %02x", pchRet1[1]); return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
} }
std::vector<uint8_t> vSocks5; std::vector<uint8_t> vSocks5;
vSocks5.push_back(0x05); // VER protocol version vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
vSocks5.push_back(0x01); // CMD CONNECT vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
vSocks5.push_back(0x00); // RSV Reserved vSocks5.push_back(0x00); // RSV Reserved must be 0
vSocks5.push_back(0x03); // ATYP DOMAINNAME vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end()); vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
vSocks5.push_back((port >> 8) & 0xFF); vSocks5.push_back((port >> 8) & 0xFF);
@ -343,7 +396,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Error sending to proxy"); return error("Error sending to proxy");
} }
char pchRet2[4]; uint8_t pchRet2[4];
if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket); CloseSocket(hSocket);
if (recvr == IntrRecvError::Timeout) { if (recvr == IntrRecvError::Timeout) {
@ -355,26 +408,26 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return error("Error while reading proxy response"); return error("Error while reading proxy response");
} }
} }
if (pchRet2[0] != 0x05) { if (pchRet2[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Proxy failed to accept request"); 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 // Failures to connect to a peer that are not proxy errors
CloseSocket(hSocket); CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1])); LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
return false; return false;
} }
if (pchRet2[2] != 0x00) { if (pchRet2[2] != 0x00) { // Reserved field must be 0
CloseSocket(hSocket); CloseSocket(hSocket);
return error("Error: malformed proxy response"); return error("Error: malformed proxy response");
} }
char pchRet3[256]; uint8_t pchRet3[256];
switch (pchRet2[3]) switch (pchRet2[3])
{ {
case 0x01: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break; case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break;
case 0x04: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break; case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break;
case 0x03: case SOCKS5Atyp::DOMAINNAME:
{ {
recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket); recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket);
if (recvr != IntrRecvError::OK) { if (recvr != IntrRecvError::OK) {