netbase: use reliable send() during SOCKS5 handshake

`send(2)` can be interrupted or for another reason it may not fully
complete sending all the bytes. We should be ready to retry the send
with the remaining bytes. This is what `Sock::SendComplete()` does,
thus use it in `Socks5()`.

Since `Sock::SendComplete()` takes a `CThreadInterrupt` argument,
change also the recv part of `Socks5()` to use `CThreadInterrupt`
instead of a boolean.

Easier reviewed with `git show -b` (ignore white-space changes).
This commit is contained in:
Vasil Dimov 2023-07-13 14:17:30 +02:00
parent 1b19d1117c
commit af0fca530e
No known key found for this signature in database
GPG Key ID: 54DF06F64B55CBBF
4 changed files with 106 additions and 112 deletions

View File

@ -3260,7 +3260,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
// Start threads
//
assert(m_msgproc);
InterruptSocks5(false);
interruptNet.reset();
flagInterruptMsgProc = false;
@ -3332,7 +3331,7 @@ void CConnman::Interrupt()
condMsgProc.notify_all();
interruptNet();
InterruptSocks5(true);
g_socks5_interrupt();
if (semOutbound) {
for (int i=0; i<m_max_outbound; i++) {

View File

@ -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;
std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup)
{
@ -269,7 +269,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)
@ -297,8 +297,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;
@ -331,6 +332,7 @@ static std::string Socks5ErrorString(uint8_t err)
bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock)
{
try {
IntrRecvError recvr;
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
if (strDest.size() > 255) {
@ -347,10 +349,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
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");
}
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);
@ -369,10 +368,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
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");
}
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) {
@ -395,10 +391,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
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");
}
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) {
@ -422,12 +415,10 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
return error("Error: malformed proxy response");
}
uint8_t pchRet3[256];
switch (pchRet2[3])
{
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");
@ -446,6 +437,9 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
}
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());
}
}
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
@ -678,11 +672,6 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out)
return false;
}
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. */

View File

@ -13,6 +13,7 @@
#include <netaddress.h>
#include <serialize.h>
#include <util/sock.h>
#include <util/threadinterrupt.h>
#include <functional>
#include <memory>
@ -220,7 +221,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
*/
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed);
void InterruptSocks5(bool interrupt);
/**
* Interrupt SOCKS5 reads or writes.
*/
extern CThreadInterrupt g_socks5_interrupt;
/**
* Connect to a specified destination service through an already connected

View File

@ -32,7 +32,9 @@ FUZZ_TARGET(socks5, .init = initialize_socks5)
ProxyCredentials proxy_credentials;
proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);
InterruptSocks5(fuzzed_data_provider.ConsumeBool());
if (fuzzed_data_provider.ConsumeBool()) {
g_socks5_interrupt();
}
// Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This
// will slow down fuzzing.
g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1ms : default_socks5_recv_timeout;