mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 06:28:31 +01:00
Merge bitcoin/bitcoin#28649: Do the SOCKS5 handshake reliably
af0fca530enetbase: use reliable send() during SOCKS5 handshake (Vasil Dimov)1b19d1117csock: change Sock::SendComplete() to take Span (Vasil Dimov) Pull request description: The `Socks5()` function which does the SOCKS5 handshake with the SOCKS5 proxy sends bytes to the socket without retrying partial writes. `send(2)` may write only part of the provided data and return. In this case the caller is responsible for retrying the operation with the remaining data. Change `Socks5()` to do that. There is already a method `Sock::SendComplete()` which does exactly that, so use it in `Socks5()`. A minor complication for this PR is that `Sock::SendComplete()` takes `std::string` argument whereas `Socks5()` has `std::vector<uint8_t>`. Thus the necessity for the first commit. It is possible to do also in other ways - convert the data in `Socks5()` to `std::string` or have just one `Sock::SendComplete()` that takes `void*` and change the callers to pass `str.data(), str.size()` or `vec.data(), vec.size()`. This came up while testing https://github.com/bitcoin/bitcoin/pull/27375. ACKs for top commit: achow101: ACKaf0fca530ejonatack: ACKaf0fca530epinheadmz: ACKaf0fca530eTree-SHA512: 1d4a53d0628f7607378038ac56dc3b8624ce9322b034c9547a0c3ce052eafb4b18213f258aa3b57bcb4d990a5e0548a37ec70af2bd55f6e8e6399936f1ce047a
This commit is contained in:
205
src/netbase.cpp
205
src/netbase.cpp
@@ -30,7 +30,7 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP;
|
||||
|
||||
// Need ample time for negotiation for very slow proxies such as Tor
|
||||
std::chrono::milliseconds g_socks5_recv_timeout = 20s;
|
||||
static std::atomic<bool> interruptSocks5Recv(false);
|
||||
CThreadInterrupt g_socks5_interrupt;
|
||||
|
||||
ReachableNets g_reachable_nets;
|
||||
|
||||
@@ -271,7 +271,7 @@ enum class IntrRecvError {
|
||||
* IntrRecvError::OK only if all of the specified number of bytes were
|
||||
* read.
|
||||
*
|
||||
* @see This function can be interrupted by calling InterruptSocks5(bool).
|
||||
* @see This function can be interrupted by calling g_socks5_interrupt().
|
||||
* Sockets can be made non-blocking with Sock::SetNonBlocking().
|
||||
*/
|
||||
static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::milliseconds timeout, const Sock& sock)
|
||||
@@ -299,8 +299,9 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::m
|
||||
return IntrRecvError::NetworkError;
|
||||
}
|
||||
}
|
||||
if (interruptSocks5Recv)
|
||||
if (g_socks5_interrupt) {
|
||||
return IntrRecvError::Interrupted;
|
||||
}
|
||||
curTime = Now<SteadyMilliseconds>();
|
||||
}
|
||||
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
|
||||
@@ -333,103 +334,93 @@ static std::string Socks5ErrorString(uint8_t err)
|
||||
|
||||
bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock)
|
||||
{
|
||||
IntrRecvError recvr;
|
||||
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
|
||||
if (strDest.size() > 255) {
|
||||
return error("Hostname too long");
|
||||
}
|
||||
// Construct the version identifier/method selection message
|
||||
std::vector<uint8_t> vSocks5Init;
|
||||
vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
|
||||
if (auth) {
|
||||
vSocks5Init.push_back(0x02); // 2 method identifiers follow...
|
||||
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
|
||||
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
|
||||
} else {
|
||||
vSocks5Init.push_back(0x01); // 1 method identifier follows...
|
||||
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
|
||||
}
|
||||
ssize_t ret = sock.Send(vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
|
||||
if (ret != (ssize_t)vSocks5Init.size()) {
|
||||
return error("Error sending to proxy");
|
||||
}
|
||||
uint8_t pchRet1[2];
|
||||
if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
|
||||
return false;
|
||||
}
|
||||
if (pchRet1[0] != SOCKSVersion::SOCKS5) {
|
||||
return error("Proxy failed to initialize");
|
||||
}
|
||||
if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
|
||||
// Perform username/password authentication (as described in RFC1929)
|
||||
std::vector<uint8_t> vAuth;
|
||||
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());
|
||||
vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
|
||||
vAuth.push_back(auth->password.size());
|
||||
vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
|
||||
ret = sock.Send(vAuth.data(), vAuth.size(), MSG_NOSIGNAL);
|
||||
if (ret != (ssize_t)vAuth.size()) {
|
||||
return error("Error sending authentication to proxy");
|
||||
try {
|
||||
IntrRecvError recvr;
|
||||
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
|
||||
if (strDest.size() > 255) {
|
||||
return error("Hostname too long");
|
||||
}
|
||||
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
|
||||
uint8_t pchRetA[2];
|
||||
if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
return error("Error reading proxy authentication response");
|
||||
}
|
||||
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
|
||||
return error("Proxy authentication unsuccessful");
|
||||
}
|
||||
} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
|
||||
// Perform no authentication
|
||||
} else {
|
||||
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
|
||||
}
|
||||
std::vector<uint8_t> vSocks5;
|
||||
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);
|
||||
vSocks5.push_back((port >> 0) & 0xFF);
|
||||
ret = sock.Send(vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL);
|
||||
if (ret != (ssize_t)vSocks5.size()) {
|
||||
return error("Error sending to proxy");
|
||||
}
|
||||
uint8_t pchRet2[4];
|
||||
if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
|
||||
if (recvr == IntrRecvError::Timeout) {
|
||||
/* If a timeout happens here, this effectively means we timed out while connecting
|
||||
* to the remote node. This is very common for Tor, so do not print an
|
||||
* error message. */
|
||||
return false;
|
||||
// Construct the version identifier/method selection message
|
||||
std::vector<uint8_t> vSocks5Init;
|
||||
vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
|
||||
if (auth) {
|
||||
vSocks5Init.push_back(0x02); // 2 method identifiers follow...
|
||||
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
|
||||
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
|
||||
} else {
|
||||
return error("Error while reading proxy response");
|
||||
vSocks5Init.push_back(0x01); // 1 method identifier follows...
|
||||
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
|
||||
}
|
||||
}
|
||||
if (pchRet2[0] != SOCKSVersion::SOCKS5) {
|
||||
return error("Proxy failed to accept request");
|
||||
}
|
||||
if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
|
||||
// Failures to connect to a peer that are not proxy errors
|
||||
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
|
||||
return false;
|
||||
}
|
||||
if (pchRet2[2] != 0x00) { // Reserved field must be 0
|
||||
return error("Error: malformed proxy response");
|
||||
}
|
||||
uint8_t pchRet3[256];
|
||||
switch (pchRet2[3])
|
||||
{
|
||||
sock.SendComplete(vSocks5Init, g_socks5_recv_timeout, g_socks5_interrupt);
|
||||
uint8_t pchRet1[2];
|
||||
if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
|
||||
return false;
|
||||
}
|
||||
if (pchRet1[0] != SOCKSVersion::SOCKS5) {
|
||||
return error("Proxy failed to initialize");
|
||||
}
|
||||
if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
|
||||
// Perform username/password authentication (as described in RFC1929)
|
||||
std::vector<uint8_t> vAuth;
|
||||
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());
|
||||
vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
|
||||
vAuth.push_back(auth->password.size());
|
||||
vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
|
||||
sock.SendComplete(vAuth, g_socks5_recv_timeout, g_socks5_interrupt);
|
||||
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
|
||||
uint8_t pchRetA[2];
|
||||
if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
return error("Error reading proxy authentication response");
|
||||
}
|
||||
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
|
||||
return error("Proxy authentication unsuccessful");
|
||||
}
|
||||
} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
|
||||
// Perform no authentication
|
||||
} else {
|
||||
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
|
||||
}
|
||||
std::vector<uint8_t> vSocks5;
|
||||
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);
|
||||
vSocks5.push_back((port >> 0) & 0xFF);
|
||||
sock.SendComplete(vSocks5, g_socks5_recv_timeout, g_socks5_interrupt);
|
||||
uint8_t pchRet2[4];
|
||||
if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
|
||||
if (recvr == IntrRecvError::Timeout) {
|
||||
/* If a timeout happens here, this effectively means we timed out while connecting
|
||||
* to the remote node. This is very common for Tor, so do not print an
|
||||
* error message. */
|
||||
return false;
|
||||
} else {
|
||||
return error("Error while reading proxy response");
|
||||
}
|
||||
}
|
||||
if (pchRet2[0] != SOCKSVersion::SOCKS5) {
|
||||
return error("Proxy failed to accept request");
|
||||
}
|
||||
if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
|
||||
// Failures to connect to a peer that are not proxy errors
|
||||
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
|
||||
return false;
|
||||
}
|
||||
if (pchRet2[2] != 0x00) { // Reserved field must be 0
|
||||
return error("Error: malformed proxy response");
|
||||
}
|
||||
uint8_t pchRet3[256];
|
||||
switch (pchRet2[3]) {
|
||||
case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break;
|
||||
case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break;
|
||||
case SOCKS5Atyp::DOMAINNAME:
|
||||
{
|
||||
case SOCKS5Atyp::DOMAINNAME: {
|
||||
recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock);
|
||||
if (recvr != IntrRecvError::OK) {
|
||||
return error("Error reading from proxy");
|
||||
@@ -439,15 +430,18 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
|
||||
break;
|
||||
}
|
||||
default: return error("Error: malformed proxy response");
|
||||
}
|
||||
if (recvr != IntrRecvError::OK) {
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
|
||||
return true;
|
||||
} catch (const std::runtime_error& e) {
|
||||
return error("Error during SOCKS5 proxy handshake: %s", e.what());
|
||||
}
|
||||
if (recvr != IntrRecvError::OK) {
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
|
||||
@@ -681,11 +675,6 @@ CSubNet LookupSubNet(const std::string& subnet_str)
|
||||
return subnet;
|
||||
}
|
||||
|
||||
void InterruptSocks5(bool interrupt)
|
||||
{
|
||||
interruptSocks5Recv = interrupt;
|
||||
}
|
||||
|
||||
bool IsBadPort(uint16_t port)
|
||||
{
|
||||
/* Don't forget to update doc/p2p-bad-ports.md if you change this list. */
|
||||
|
||||
Reference in New Issue
Block a user