Changeset View
Changeset View
Standalone View
Standalone View
src/netbase.cpp
Show First 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | |||||
struct timeval MillisToTimeval(int64_t nTimeout) { | struct timeval MillisToTimeval(int64_t nTimeout) { | ||||
struct timeval timeout; | struct timeval timeout; | ||||
timeout.tv_sec = nTimeout / 1000; | timeout.tv_sec = nTimeout / 1000; | ||||
timeout.tv_usec = (nTimeout % 1000) * 1000; | timeout.tv_usec = (nTimeout % 1000) * 1000; | ||||
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, | ||||
Disconnected, | Disconnected, | ||||
NetworkError, | NetworkError, | ||||
Interrupted | Interrupted | ||||
}; | }; | ||||
/** | /** | ||||
* Read bytes from socket. This will either read the full number of bytes | * Read bytes from socket. This will either read the full number of bytes | ||||
* requested or return False on error or timeout. | * requested or return False on error or timeout. | ||||
* This function can be interrupted by calling InterruptSocks5() | * This function can be interrupted by calling InterruptSocks5() | ||||
* | * | ||||
* @param data Buffer to receive into | * @param data Buffer to receive into | ||||
* @param len Length of data to receive | * @param len Length of data to receive | ||||
* @param timeout Timeout in milliseconds for receive operation | * @param timeout Timeout in milliseconds for receive operation | ||||
* | * | ||||
* @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, | static IntrRecvError InterruptibleRecv(uint8_t *data, size_t len, int timeout, | ||||
const SOCKET &hSocket) { | const SOCKET &hSocket) { | ||||
int64_t curTime = GetTimeMillis(); | int64_t curTime = GetTimeMillis(); | ||||
int64_t endTime = curTime + timeout; | int64_t endTime = curTime + timeout; | ||||
// Maximum time to wait in one select call. It will take up until this time | // 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. | // (in millis) 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) { | ||||
// Optimistically try the recv first | // Optimistically try the recv first | ||||
ssize_t ret = recv(hSocket, data, len, 0); | ssize_t ret = recv(hSocket, (char *)data, len, 0); | ||||
if (ret > 0) { | if (ret > 0) { | ||||
len -= ret; | len -= ret; | ||||
data += ret; | data += ret; | ||||
} else if (ret == 0) { | } else if (ret == 0) { | ||||
// Unexpected disconnection | // Unexpected disconnection | ||||
return IntrRecvError::Disconnected; | return IntrRecvError::Disconnected; | ||||
} else { | } else { | ||||
// Other error or blocking | // Other error or blocking | ||||
Show All 19 Lines | while (len > 0 && curTime < endTime) { | ||||
if (interruptSocks5Recv) { | if (interruptSocks5Recv) { | ||||
return IntrRecvError::Interrupted; | return IntrRecvError::Interrupted; | ||||
} | } | ||||
curTime = GetTimeMillis(); | curTime = GetTimeMillis(); | ||||
} | } | ||||
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: | case SOCKS5Reply::GENFAILURE: | ||||
return "general failure"; | return "general failure"; | ||||
case 0x02: | case SOCKS5Reply::NOTALLOWED: | ||||
return "connection not allowed"; | return "connection not allowed"; | ||||
case 0x03: | case SOCKS5Reply::NETUNREACHABLE: | ||||
return "network unreachable"; | return "network unreachable"; | ||||
case 0x04: | case SOCKS5Reply::HOSTUNREACHABLE: | ||||
return "host unreachable"; | return "host unreachable"; | ||||
case 0x05: | case SOCKS5Reply::CONNREFUSED: | ||||
return "connection refused"; | return "connection refused"; | ||||
case 0x06: | case SOCKS5Reply::TTLEXPIRED: | ||||
return "TTL expired"; | return "TTL expired"; | ||||
case 0x07: | case SOCKS5Reply::CMDUNSUPPORTED: | ||||
return "protocol error"; | return "protocol error"; | ||||
case 0x08: | case SOCKS5Reply::ATYPEUNSUPPORTED: | ||||
return "address type not supported"; | return "address type not supported"; | ||||
default: | default: | ||||
return "unknown"; | return "unknown"; | ||||
} | } | ||||
} | } | ||||
/** Connect using SOCKS5 (as described in RFC1928) */ | /** Connect using SOCKS5 (as described in RFC1928) */ | ||||
static bool Socks5(const std::string &strDest, int port, | static bool Socks5(const std::string &strDest, int port, | ||||
const ProxyCredentials *auth, SOCKET &hSocket) { | const ProxyCredentials *auth, SOCKET &hSocket) { | ||||
IntrRecvError recvr; | IntrRecvError recvr; | ||||
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); | LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); | ||||
if (strDest.size() > 255) { | if (strDest.size() > 255) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
return error("Hostname too long"); | return error("Hostname too long"); | ||||
} | } | ||||
// 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) { | ||||
// # METHODS | vSocks5Init.push_back(0x02); // Number of methods | ||||
vSocks5Init.push_back(0x02); | vSocks5Init.push_back(SOCKS5Method::NOAUTH); | ||||
// X'00' NO AUTHENTICATION REQUIRED | vSocks5Init.push_back(SOCKS5Method::USER_PASS); | ||||
vSocks5Init.push_back(0x00); | |||||
// X'02' USERNAME/PASSWORD (RFC1929) | |||||
vSocks5Init.push_back(0x02); | |||||
} else { | } else { | ||||
// # METHODS | vSocks5Init.push_back(0x01); // Number of methods | ||||
vSocks5Init.push_back(0x01); | vSocks5Init.push_back(SOCKS5Method::NOAUTH); | ||||
// X'00' NO AUTHENTICATION REQUIRED | |||||
vSocks5Init.push_back(0x00); | |||||
} | } | ||||
ssize_t ret = send(hSocket, (const char *)vSocks5Init.data(), | ssize_t ret = send(hSocket, (const char *)vSocks5Init.data(), | ||||
vSocks5Init.size(), MSG_NOSIGNAL); | 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)) != | if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != | ||||
IntrRecvError::OK) { | IntrRecvError::OK) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() " | LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() " | ||||
"timeout or other failure\n", | "timeout or other failure\n", | ||||
strDest, port); | 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; | ||||
// Current (and only) version of user/pass subnegotiation | |||||
vAuth.push_back(0x01); | vAuth.push_back(0x01); | ||||
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()); | ||||
vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); | vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); | ||||
vAuth.push_back(auth->password.size()); | vAuth.push_back(auth->password.size()); | ||||
vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); | vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); | ||||
ret = send(hSocket, (const char *)vAuth.data(), vAuth.size(), | ret = send(hSocket, (const char *)vAuth.data(), vAuth.size(), | ||||
MSG_NOSIGNAL); | MSG_NOSIGNAL); | ||||
if (ret != (ssize_t)vAuth.size()) { | if (ret != (ssize_t)vAuth.size()) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
return error("Error sending authentication to proxy"); | return error("Error sending authentication to proxy"); | ||||
} | } | ||||
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", | LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", | ||||
auth->username, auth->password); | auth->username, auth->password); | ||||
char pchRetA[2]; | uint8_t pchRetA[2]; | ||||
if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, | if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, | ||||
hSocket)) != IntrRecvError::OK) { | hSocket)) != IntrRecvError::OK) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
return error("Error reading proxy authentication response"); | return error("Error reading proxy authentication response"); | ||||
} | } | ||||
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { | if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { | ||||
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", | return error("Proxy requested wrong authentication method %02x", | ||||
pchRet1[1]); | pchRet1[1]); | ||||
} | } | ||||
std::vector<uint8_t> vSocks5; | std::vector<uint8_t> vSocks5; | ||||
// VER protocol version | // VER protocol version | ||||
vSocks5.push_back(0x05); | vSocks5.push_back(SOCKSVersion::SOCKS5); | ||||
// CMD CONNECT | // CMD CONNECT | ||||
vSocks5.push_back(0x01); | vSocks5.push_back(SOCKS5Command::CONNECT); | ||||
// RSV Reserved | // RSV Reserved must be 0 | ||||
vSocks5.push_back(0x00); | vSocks5.push_back(0x00); | ||||
// ATYP DOMAINNAME | // ATYP DOMAINNAME | ||||
vSocks5.push_back(0x03); | vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); | ||||
// Length<=255 is checked at beginning of function | // Length<=255 is checked at beginning of function | ||||
vSocks5.push_back(strDest.size()); | vSocks5.push_back(strDest.size()); | ||||
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); | ||||
vSocks5.push_back((port >> 0) & 0xFF); | vSocks5.push_back((port >> 0) & 0xFF); | ||||
ret = send(hSocket, (const char *)vSocks5.data(), vSocks5.size(), | ret = send(hSocket, (const char *)vSocks5.data(), vSocks5.size(), | ||||
MSG_NOSIGNAL); | MSG_NOSIGNAL); | ||||
if (ret != (ssize_t)vSocks5.size()) { | if (ret != (ssize_t)vSocks5.size()) { | ||||
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)) != | if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != | ||||
IntrRecvError::OK) { | IntrRecvError::OK) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
if (recvr == IntrRecvError::Timeout) { | if (recvr == IntrRecvError::Timeout) { | ||||
/** | /** | ||||
* If a timeout happens here, this effectively means we timed out | * If a timeout happens here, this effectively means we timed out | ||||
* while connecting to the remote node. This is very common for Tor, | * while connecting to the remote node. This is very common for Tor, | ||||
* so do not print an error message. | * so do not print an error message. | ||||
*/ | */ | ||||
return false; | return false; | ||||
} else { | } else { | ||||
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, | LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, | ||||
Socks5ErrorString(pchRet2[1])); | Socks5ErrorString(pchRet2[1])); | ||||
return false; | return false; | ||||
} | } | ||||
// Reserved field must be 0 | |||||
if (pchRet2[2] != 0x00) { | if (pchRet2[2] != 0x00) { | ||||
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: | case SOCKS5Atyp::IPV4: | ||||
recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); | recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); | ||||
break; | break; | ||||
case 0x04: | case SOCKS5Atyp::IPV6: | ||||
recvr = | recvr = | ||||
InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); | InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); | ||||
break; | 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) { | ||||
CloseSocket(hSocket); | CloseSocket(hSocket); | ||||
return error("Error reading from proxy"); | return error("Error reading from proxy"); | ||||
} | } | ||||
int nRecv = pchRet3[0]; | int nRecv = pchRet3[0]; | ||||
recvr = | recvr = | ||||
InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket); | InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket); | ||||
▲ Show 20 Lines • Show All 294 Lines • Show Last 20 Lines |