net: Improve and document SOCKS code
Make the SOCKS code more consistent, and document the constants used.
This commit is contained in:
parent
10a20bf770
commit
22f816ef4d
1 changed files with 89 additions and 36 deletions
125
src/netbase.cpp
125
src/netbase.cpp
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue