netbase: Make SOCKS5 negotiation interruptible
Avoids that SOCKS5 negotiation will hold up the shutdown process. - Sockets can stay in non-blocking mode, no need to switch it on/off anymore - Adds a timeout (20 seconds) on SOCK5 negotiation. This should be enough for even Tor to get a connection to a hidden service, and avoids blocking the opencon thread indefinitely on a hanging proxy. Fixes #2954.
This commit is contained in:
parent
6a8d15cc16
commit
6050ab6855
2 changed files with 70 additions and 22 deletions
|
@ -488,10 +488,6 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
|
|||
{
|
||||
addrman.Attempt(addrConnect);
|
||||
|
||||
// Set to non-blocking
|
||||
if (!SetSocketNonBlocking(hSocket, true))
|
||||
LogPrintf("ConnectNode: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
|
||||
// Add node
|
||||
CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false);
|
||||
pnode->AddRef();
|
||||
|
|
|
@ -45,6 +45,9 @@ bool fNameLookup = false;
|
|||
|
||||
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
|
||||
|
||||
// Need ample time for negotiation for very slow proxies such as Tor (milliseconds)
|
||||
static const int SOCKS5_RECV_TIMEOUT = 20 * 1000;
|
||||
|
||||
enum Network ParseNetwork(std::string net) {
|
||||
boost::to_lower(net);
|
||||
if (net == "ipv4") return NET_IPV4;
|
||||
|
@ -225,6 +228,63 @@ bool LookupNumeric(const char *pszName, CService& addr, int portDefault)
|
|||
return Lookup(pszName, addr, portDefault, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert milliseconds to a struct timeval for select.
|
||||
*/
|
||||
struct timeval static MillisToTimeval(int64_t nTimeout)
|
||||
{
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = nTimeout / 1000;
|
||||
timeout.tv_usec = (nTimeout % 1000) * 1000;
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from socket. This will either read the full number of bytes requested
|
||||
* or return False on error or timeout.
|
||||
* This function can be interrupted by boost thread interrupt.
|
||||
*
|
||||
* @param data Buffer to receive into
|
||||
* @param len Length of data to receive
|
||||
* @param timeout Timeout in milliseconds for receive operation
|
||||
*
|
||||
* @note This function requires that hSocket is in non-blocking mode.
|
||||
*/
|
||||
bool static InterruptibleRecv(char* data, size_t len, int timeout, SOCKET& hSocket)
|
||||
{
|
||||
int64_t curTime = GetTimeMillis();
|
||||
int64_t endTime = curTime + timeout;
|
||||
// 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.
|
||||
const int64_t maxWait = 1000;
|
||||
while (len > 0 && curTime < endTime) {
|
||||
ssize_t ret = recv(hSocket, data, len, 0); // Optimistically try the recv first
|
||||
if (ret > 0) {
|
||||
len -= ret;
|
||||
data += ret;
|
||||
} else if (ret == 0) { // Unexpected disconnection
|
||||
return false;
|
||||
} else { // Other error or blocking
|
||||
int nErr = WSAGetLastError();
|
||||
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) {
|
||||
struct timeval tval = MillisToTimeval(std::min(endTime - curTime, maxWait));
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(hSocket, &fdset);
|
||||
int nRet = select(hSocket + 1, &fdset, NULL, NULL, &tval);
|
||||
if (nRet == SOCKET_ERROR) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boost::this_thread::interruption_point();
|
||||
curTime = GetTimeMillis();
|
||||
}
|
||||
return len == 0;
|
||||
}
|
||||
|
||||
bool static Socks5(string strDest, int port, SOCKET& hSocket)
|
||||
{
|
||||
LogPrintf("SOCKS5 connecting %s\n", strDest);
|
||||
|
@ -243,7 +303,7 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
|
|||
return error("Error sending to proxy");
|
||||
}
|
||||
char pchRet1[2];
|
||||
if (recv(hSocket, pchRet1, 2, 0) != 2)
|
||||
if (!InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket))
|
||||
{
|
||||
CloseSocket(hSocket);
|
||||
return error("Error reading proxy response");
|
||||
|
@ -266,7 +326,7 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
|
|||
return error("Error sending to proxy");
|
||||
}
|
||||
char pchRet2[4];
|
||||
if (recv(hSocket, pchRet2, 4, 0) != 4)
|
||||
if (!InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket))
|
||||
{
|
||||
CloseSocket(hSocket);
|
||||
return error("Error reading proxy response");
|
||||
|
@ -300,27 +360,27 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
|
|||
char pchRet3[256];
|
||||
switch (pchRet2[3])
|
||||
{
|
||||
case 0x01: ret = recv(hSocket, pchRet3, 4, 0) != 4; break;
|
||||
case 0x04: ret = recv(hSocket, pchRet3, 16, 0) != 16; break;
|
||||
case 0x01: ret = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break;
|
||||
case 0x04: ret = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break;
|
||||
case 0x03:
|
||||
{
|
||||
ret = recv(hSocket, pchRet3, 1, 0) != 1;
|
||||
if (ret) {
|
||||
ret = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket);
|
||||
if (!ret) {
|
||||
CloseSocket(hSocket);
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
int nRecv = pchRet3[0];
|
||||
ret = recv(hSocket, pchRet3, nRecv, 0) != nRecv;
|
||||
ret = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket);
|
||||
break;
|
||||
}
|
||||
default: CloseSocket(hSocket); return error("Error: malformed proxy response");
|
||||
}
|
||||
if (ret)
|
||||
if (!ret)
|
||||
{
|
||||
CloseSocket(hSocket);
|
||||
return error("Error reading from proxy");
|
||||
}
|
||||
if (recv(hSocket, pchRet3, 2, 0) != 2)
|
||||
if (!InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, hSocket))
|
||||
{
|
||||
CloseSocket(hSocket);
|
||||
return error("Error reading from proxy");
|
||||
|
@ -360,10 +420,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
|
|||
// WSAEINVAL is here because some legacy version of winsock uses it
|
||||
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL)
|
||||
{
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = nTimeout / 1000;
|
||||
timeout.tv_usec = (nTimeout % 1000) * 1000;
|
||||
|
||||
struct timeval timeout = MillisToTimeval(nTimeout);
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(hSocket, &fdset);
|
||||
|
@ -410,11 +467,6 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
|
|||
}
|
||||
}
|
||||
|
||||
// This is required when using SOCKS5 proxy!
|
||||
// CNode::ConnectNode turns the socket back to non-blocking.
|
||||
if (!SetSocketNonBlocking(hSocket, false))
|
||||
return error("ConnectSocketDirectly: Setting socket to blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
|
||||
|
||||
hSocketRet = hSocket;
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue