|
|
|
@ -184,6 +184,48 @@ struct timeval MillisToTimeval(int64_t nTimeout)
@@ -184,6 +184,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, |
|
|
|
@ -203,7 +245,7 @@ enum class IntrRecvError {
@@ -203,7 +245,7 @@ enum class IntrRecvError {
|
|
|
|
|
* |
|
|
|
|
* @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 endTime = curTime + timeout; |
|
|
|
@ -211,7 +253,7 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
@@ -211,7 +253,7 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
|
|
|
|
|
// 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; |
|
|
|
@ -242,24 +284,35 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
@@ -242,24 +284,35 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
|
|
|
|
|
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Credentials for proxy authentication */ |
|
|
|
|
struct ProxyCredentials |
|
|
|
|
{ |
|
|
|
|
std::string username; |
|
|
|
|
std::string password; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
std::string Socks5ErrorString(int err) |
|
|
|
|
/** Convert SOCKS5 reply to a an error message */ |
|
|
|
|
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"; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -274,34 +327,34 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
@@ -274,34 +327,34 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
|
|
|
|
|
} |
|
|
|
|
// 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()); |
|
|
|
@ -314,7 +367,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
@@ -314,7 +367,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
|
|
|
|
|
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"); |
|
|
|
@ -323,17 +376,17 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
@@ -323,17 +376,17 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
|
|
|
|
|
CloseSocket(hSocket); |
|
|
|
|
return error("Proxy authentication unsuccessful"); |
|
|
|
|
} |
|
|
|
|
} 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); |
|
|
|
@ -343,7 +396,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
@@ -343,7 +396,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
|
|
|
|
|
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) { |
|
|
|
@ -355,26 +408,26 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
@@ -355,26 +408,26 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
|
|
|
|
|
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]; |
|
|
|
|
uint8_t 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: |
|
|
|
|
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) { |
|
|
|
|