Skip to content

Commit af0fca5

Browse files
committed
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).
1 parent 1b19d11 commit af0fca5

File tree

4 files changed

+104
-110
lines changed

4 files changed

+104
-110
lines changed

src/net.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,7 +3260,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
32603260
// Start threads
32613261
//
32623262
assert(m_msgproc);
3263-
InterruptSocks5(false);
32643263
interruptNet.reset();
32653264
flagInterruptMsgProc = false;
32663265

@@ -3332,7 +3331,7 @@ void CConnman::Interrupt()
33323331
condMsgProc.notify_all();
33333332

33343333
interruptNet();
3335-
InterruptSocks5(true);
3334+
g_socks5_interrupt();
33363335

33373336
if (semOutbound) {
33383337
for (int i=0; i<m_max_outbound; i++) {

src/netbase.cpp

Lines changed: 95 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP;
3030

3131
// Need ample time for negotiation for very slow proxies such as Tor
3232
std::chrono::milliseconds g_socks5_recv_timeout = 20s;
33-
static std::atomic<bool> interruptSocks5Recv(false);
33+
CThreadInterrupt g_socks5_interrupt;
3434

3535
std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup)
3636
{
@@ -269,7 +269,7 @@ enum class IntrRecvError {
269269
* IntrRecvError::OK only if all of the specified number of bytes were
270270
* read.
271271
*
272-
* @see This function can be interrupted by calling InterruptSocks5(bool).
272+
* @see This function can be interrupted by calling g_socks5_interrupt().
273273
* Sockets can be made non-blocking with Sock::SetNonBlocking().
274274
*/
275275
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
297297
return IntrRecvError::NetworkError;
298298
}
299299
}
300-
if (interruptSocks5Recv)
300+
if (g_socks5_interrupt) {
301301
return IntrRecvError::Interrupted;
302+
}
302303
curTime = Now<SteadyMilliseconds>();
303304
}
304305
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
@@ -331,103 +332,93 @@ static std::string Socks5ErrorString(uint8_t err)
331332

332333
bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock)
333334
{
334-
IntrRecvError recvr;
335-
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
336-
if (strDest.size() > 255) {
337-
return error("Hostname too long");
338-
}
339-
// Construct the version identifier/method selection message
340-
std::vector<uint8_t> vSocks5Init;
341-
vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
342-
if (auth) {
343-
vSocks5Init.push_back(0x02); // 2 method identifiers follow...
344-
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
345-
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
346-
} else {
347-
vSocks5Init.push_back(0x01); // 1 method identifier follows...
348-
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
349-
}
350-
ssize_t ret = sock.Send(vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
351-
if (ret != (ssize_t)vSocks5Init.size()) {
352-
return error("Error sending to proxy");
353-
}
354-
uint8_t pchRet1[2];
355-
if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
356-
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
357-
return false;
358-
}
359-
if (pchRet1[0] != SOCKSVersion::SOCKS5) {
360-
return error("Proxy failed to initialize");
361-
}
362-
if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
363-
// Perform username/password authentication (as described in RFC1929)
364-
std::vector<uint8_t> vAuth;
365-
vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation
366-
if (auth->username.size() > 255 || auth->password.size() > 255)
367-
return error("Proxy username or password too long");
368-
vAuth.push_back(auth->username.size());
369-
vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
370-
vAuth.push_back(auth->password.size());
371-
vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
372-
ret = sock.Send(vAuth.data(), vAuth.size(), MSG_NOSIGNAL);
373-
if (ret != (ssize_t)vAuth.size()) {
374-
return error("Error sending authentication to proxy");
375-
}
376-
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
377-
uint8_t pchRetA[2];
378-
if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
379-
return error("Error reading proxy authentication response");
335+
try {
336+
IntrRecvError recvr;
337+
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
338+
if (strDest.size() > 255) {
339+
return error("Hostname too long");
380340
}
381-
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
382-
return error("Proxy authentication unsuccessful");
341+
// Construct the version identifier/method selection message
342+
std::vector<uint8_t> vSocks5Init;
343+
vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
344+
if (auth) {
345+
vSocks5Init.push_back(0x02); // 2 method identifiers follow...
346+
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
347+
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
348+
} else {
349+
vSocks5Init.push_back(0x01); // 1 method identifier follows...
350+
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
383351
}
384-
} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
385-
// Perform no authentication
386-
} else {
387-
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
388-
}
389-
std::vector<uint8_t> vSocks5;
390-
vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
391-
vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
392-
vSocks5.push_back(0x00); // RSV Reserved must be 0
393-
vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
394-
vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
395-
vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
396-
vSocks5.push_back((port >> 8) & 0xFF);
397-
vSocks5.push_back((port >> 0) & 0xFF);
398-
ret = sock.Send(vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL);
399-
if (ret != (ssize_t)vSocks5.size()) {
400-
return error("Error sending to proxy");
401-
}
402-
uint8_t pchRet2[4];
403-
if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
404-
if (recvr == IntrRecvError::Timeout) {
405-
/* If a timeout happens here, this effectively means we timed out while connecting
406-
* to the remote node. This is very common for Tor, so do not print an
407-
* error message. */
352+
sock.SendComplete(vSocks5Init, g_socks5_recv_timeout, g_socks5_interrupt);
353+
uint8_t pchRet1[2];
354+
if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
355+
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
408356
return false;
357+
}
358+
if (pchRet1[0] != SOCKSVersion::SOCKS5) {
359+
return error("Proxy failed to initialize");
360+
}
361+
if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
362+
// Perform username/password authentication (as described in RFC1929)
363+
std::vector<uint8_t> vAuth;
364+
vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation
365+
if (auth->username.size() > 255 || auth->password.size() > 255)
366+
return error("Proxy username or password too long");
367+
vAuth.push_back(auth->username.size());
368+
vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
369+
vAuth.push_back(auth->password.size());
370+
vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
371+
sock.SendComplete(vAuth, g_socks5_recv_timeout, g_socks5_interrupt);
372+
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
373+
uint8_t pchRetA[2];
374+
if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
375+
return error("Error reading proxy authentication response");
376+
}
377+
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
378+
return error("Proxy authentication unsuccessful");
379+
}
380+
} else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
381+
// Perform no authentication
409382
} else {
410-
return error("Error while reading proxy response");
383+
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
411384
}
412-
}
413-
if (pchRet2[0] != SOCKSVersion::SOCKS5) {
414-
return error("Proxy failed to accept request");
415-
}
416-
if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
417-
// Failures to connect to a peer that are not proxy errors
418-
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
419-
return false;
420-
}
421-
if (pchRet2[2] != 0x00) { // Reserved field must be 0
422-
return error("Error: malformed proxy response");
423-
}
424-
uint8_t pchRet3[256];
425-
switch (pchRet2[3])
426-
{
385+
std::vector<uint8_t> vSocks5;
386+
vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
387+
vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
388+
vSocks5.push_back(0x00); // RSV Reserved must be 0
389+
vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
390+
vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
391+
vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
392+
vSocks5.push_back((port >> 8) & 0xFF);
393+
vSocks5.push_back((port >> 0) & 0xFF);
394+
sock.SendComplete(vSocks5, g_socks5_recv_timeout, g_socks5_interrupt);
395+
uint8_t pchRet2[4];
396+
if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
397+
if (recvr == IntrRecvError::Timeout) {
398+
/* If a timeout happens here, this effectively means we timed out while connecting
399+
* to the remote node. This is very common for Tor, so do not print an
400+
* error message. */
401+
return false;
402+
} else {
403+
return error("Error while reading proxy response");
404+
}
405+
}
406+
if (pchRet2[0] != SOCKSVersion::SOCKS5) {
407+
return error("Proxy failed to accept request");
408+
}
409+
if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
410+
// Failures to connect to a peer that are not proxy errors
411+
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
412+
return false;
413+
}
414+
if (pchRet2[2] != 0x00) { // Reserved field must be 0
415+
return error("Error: malformed proxy response");
416+
}
417+
uint8_t pchRet3[256];
418+
switch (pchRet2[3]) {
427419
case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break;
428420
case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break;
429-
case SOCKS5Atyp::DOMAINNAME:
430-
{
421+
case SOCKS5Atyp::DOMAINNAME: {
431422
recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock);
432423
if (recvr != IntrRecvError::OK) {
433424
return error("Error reading from proxy");
@@ -437,15 +428,18 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
437428
break;
438429
}
439430
default: return error("Error: malformed proxy response");
431+
}
432+
if (recvr != IntrRecvError::OK) {
433+
return error("Error reading from proxy");
434+
}
435+
if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
436+
return error("Error reading from proxy");
437+
}
438+
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
439+
return true;
440+
} catch (const std::runtime_error& e) {
441+
return error("Error during SOCKS5 proxy handshake: %s", e.what());
440442
}
441-
if (recvr != IntrRecvError::OK) {
442-
return error("Error reading from proxy");
443-
}
444-
if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
445-
return error("Error reading from proxy");
446-
}
447-
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
448-
return true;
449443
}
450444

451445
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
@@ -678,11 +672,6 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out)
678672
return false;
679673
}
680674

681-
void InterruptSocks5(bool interrupt)
682-
{
683-
interruptSocks5Recv = interrupt;
684-
}
685-
686675
bool IsBadPort(uint16_t port)
687676
{
688677
/* Don't forget to update doc/p2p-bad-ports.md if you change this list. */

src/netbase.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <netaddress.h>
1414
#include <serialize.h>
1515
#include <util/sock.h>
16+
#include <util/threadinterrupt.h>
1617

1718
#include <functional>
1819
#include <memory>
@@ -220,7 +221,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
220221
*/
221222
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed);
222223

223-
void InterruptSocks5(bool interrupt);
224+
/**
225+
* Interrupt SOCKS5 reads or writes.
226+
*/
227+
extern CThreadInterrupt g_socks5_interrupt;
224228

225229
/**
226230
* Connect to a specified destination service through an already connected

src/test/fuzz/socks5.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ FUZZ_TARGET(socks5, .init = initialize_socks5)
3232
ProxyCredentials proxy_credentials;
3333
proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
3434
proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);
35-
InterruptSocks5(fuzzed_data_provider.ConsumeBool());
35+
if (fuzzed_data_provider.ConsumeBool()) {
36+
g_socks5_interrupt();
37+
}
3638
// Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This
3739
// will slow down fuzzing.
3840
g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1ms : default_socks5_recv_timeout;

0 commit comments

Comments
 (0)