Skip to content

Commit 0528cfd

Browse files
committed
Merge bitcoin#28649: Do the SOCKS5 handshake reliably
af0fca5 netbase: use reliable send() during SOCKS5 handshake (Vasil Dimov) 1b19d11 sock: 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 bitcoin#27375. ACKs for top commit: achow101: ACK af0fca5 jonatack: ACK af0fca5 pinheadmz: ACK af0fca5 Tree-SHA512: 1d4a53d0628f7607378038ac56dc3b8624ce9322b034c9547a0c3ce052eafb4b18213f258aa3b57bcb4d990a5e0548a37ec70af2bd55f6e8e6399936f1ce047a
2 parents 3da69c4 + af0fca5 commit 0528cfd

File tree

6 files changed

+120
-112
lines changed

6 files changed

+120
-112
lines changed

src/net.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,7 +3219,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
32193219
// Start threads
32203220
//
32213221
assert(m_msgproc);
3222-
InterruptSocks5(false);
32233222
interruptNet.reset();
32243223
flagInterruptMsgProc = false;
32253224

@@ -3291,7 +3290,7 @@ void CConnman::Interrupt()
32913290
condMsgProc.notify_all();
32923291

32933292
interruptNet();
3294-
InterruptSocks5(true);
3293+
g_socks5_interrupt();
32953294

32963295
if (semOutbound) {
32973296
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
ReachableNets g_reachable_nets;
3636

@@ -271,7 +271,7 @@ enum class IntrRecvError {
271271
* IntrRecvError::OK only if all of the specified number of bytes were
272272
* read.
273273
*
274-
* @see This function can be interrupted by calling InterruptSocks5(bool).
274+
* @see This function can be interrupted by calling g_socks5_interrupt().
275275
* Sockets can be made non-blocking with Sock::SetNonBlocking().
276276
*/
277277
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
299299
return IntrRecvError::NetworkError;
300300
}
301301
}
302-
if (interruptSocks5Recv)
302+
if (g_socks5_interrupt) {
303303
return IntrRecvError::Interrupted;
304+
}
304305
curTime = Now<SteadyMilliseconds>();
305306
}
306307
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
@@ -333,103 +334,93 @@ static std::string Socks5ErrorString(uint8_t err)
333334

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

453447
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
@@ -681,11 +675,6 @@ CSubNet LookupSubNet(const std::string& subnet_str)
681675
return subnet;
682676
}
683677

684-
void InterruptSocks5(bool interrupt)
685-
{
686-
interruptSocks5Recv = interrupt;
687-
}
688-
689678
bool IsBadPort(uint16_t port)
690679
{
691680
/* 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>
@@ -274,7 +275,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
274275
*/
275276
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed);
276277

277-
void InterruptSocks5(bool interrupt);
278+
/**
279+
* Interrupt SOCKS5 reads or writes.
280+
*/
281+
extern CThreadInterrupt g_socks5_interrupt;
278282

279283
/**
280284
* 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;

src/util/sock.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ bool Sock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per
242242
#endif /* USE_POLL */
243243
}
244244

245-
void Sock::SendComplete(const std::string& data,
245+
void Sock::SendComplete(Span<const unsigned char> data,
246246
std::chrono::milliseconds timeout,
247247
CThreadInterrupt& interrupt) const
248248
{
@@ -283,6 +283,13 @@ void Sock::SendComplete(const std::string& data,
283283
}
284284
}
285285

286+
void Sock::SendComplete(Span<const char> data,
287+
std::chrono::milliseconds timeout,
288+
CThreadInterrupt& interrupt) const
289+
{
290+
SendComplete(MakeUCharSpan(data), timeout, interrupt);
291+
}
292+
286293
std::string Sock::RecvUntilTerminator(uint8_t terminator,
287294
std::chrono::milliseconds timeout,
288295
CThreadInterrupt& interrupt,

src/util/sock.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,14 @@ class Sock
228228
* @throws std::runtime_error if the operation cannot be completed. In this case only some of
229229
* the data will be written to the socket.
230230
*/
231-
virtual void SendComplete(const std::string& data,
231+
virtual void SendComplete(Span<const unsigned char> data,
232+
std::chrono::milliseconds timeout,
233+
CThreadInterrupt& interrupt) const;
234+
235+
/**
236+
* Convenience method, equivalent to `SendComplete(MakeUCharSpan(data), timeout, interrupt)`.
237+
*/
238+
virtual void SendComplete(Span<const char> data,
232239
std::chrono::milliseconds timeout,
233240
CThreadInterrupt& interrupt) const;
234241

0 commit comments

Comments
 (0)