aboutsummaryrefslogtreecommitdiff
path: root/src/netbase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/netbase.cpp')
-rw-r--r--src/netbase.cpp226
1 files changed, 153 insertions, 73 deletions
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 067cfa024..5819c152a 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -7,16 +7,17 @@
#include "bitcoin-config.h"
#endif
-#ifdef HAVE_GETADDRINFO_A
-#include <netdb.h>
-#endif
-
#include "netbase.h"
#include "hash.h"
#include "sync.h"
#include "uint256.h"
#include "util.h"
+#include "utilstrencodings.h"
+
+#ifdef HAVE_GETADDRINFO_A
+#include <netdb.h>
+#endif
#ifndef WIN32
#if HAVE_INET_PTON
@@ -27,6 +28,7 @@
#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
#include <boost/algorithm/string/predicate.hpp> // for startswith() and endswith()
+#include <boost/thread.hpp>
#if !defined(HAVE_MSG_NOSIGNAL) && !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
@@ -43,14 +45,27 @@ 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;
if (net == "ipv6") return NET_IPV6;
- if (net == "tor") return NET_TOR;
+ if (net == "tor" || net == "onion") return NET_TOR;
return NET_UNROUTABLE;
}
+std::string GetNetworkName(enum Network net) {
+ switch(net)
+ {
+ case NET_IPV4: return "ipv4";
+ case NET_IPV6: return "ipv6";
+ case NET_TOR: return "onion";
+ default: return "";
+ }
+}
+
void SplitHostPort(std::string in, int &portOut, std::string &hostOut) {
size_t colon = in.find_last_of(':');
// if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator
@@ -213,12 +228,69 @@ 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);
if (strDest.size() > 255)
{
- closesocket(hSocket);
+ CloseSocket(hSocket);
return error("Hostname too long");
}
char pszSocks5Init[] = "\5\1\0";
@@ -227,18 +299,18 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
ssize_t ret = send(hSocket, pszSocks5Init, nSize, MSG_NOSIGNAL);
if (ret != nSize)
{
- closesocket(hSocket);
+ CloseSocket(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);
+ CloseSocket(hSocket);
return error("Error reading proxy response");
}
if (pchRet1[0] != 0x05 || pchRet1[1] != 0x00)
{
- closesocket(hSocket);
+ CloseSocket(hSocket);
return error("Proxy failed to initialize");
}
string strSocks5("\5\1");
@@ -247,26 +319,26 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
strSocks5 += strDest;
strSocks5 += static_cast<char>((port >> 8) & 0xFF);
strSocks5 += static_cast<char>((port >> 0) & 0xFF);
- ret = send(hSocket, strSocks5.c_str(), strSocks5.size(), MSG_NOSIGNAL);
+ ret = send(hSocket, strSocks5.data(), strSocks5.size(), MSG_NOSIGNAL);
if (ret != (ssize_t)strSocks5.size())
{
- closesocket(hSocket);
+ CloseSocket(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);
+ CloseSocket(hSocket);
return error("Error reading proxy response");
}
if (pchRet2[0] != 0x05)
{
- closesocket(hSocket);
+ CloseSocket(hSocket);
return error("Proxy failed to accept request");
}
if (pchRet2[1] != 0x00)
{
- closesocket(hSocket);
+ CloseSocket(hSocket);
switch (pchRet2[1])
{
case 0x01: return error("Proxy error: general failure");
@@ -282,35 +354,35 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket)
}
if (pchRet2[2] != 0x00)
{
- closesocket(hSocket);
+ CloseSocket(hSocket);
return error("Error: malformed proxy response");
}
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) {
- closesocket(hSocket);
+ 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");
+ default: CloseSocket(hSocket); return error("Error: malformed proxy response");
}
- if (ret)
+ if (!ret)
{
- closesocket(hSocket);
+ 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);
+ CloseSocket(hSocket);
return error("Error reading from proxy");
}
LogPrintf("SOCKS5 connected %s\n", strDest);
@@ -331,22 +403,16 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET)
return false;
+
#ifdef SO_NOSIGPIPE
int set = 1;
+ // Different way of disabling SIGPIPE on BSD
setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));
#endif
-#ifdef WIN32
- u_long fNonblock = 1;
- if (ioctlsocket(hSocket, FIONBIO, &fNonblock) == SOCKET_ERROR)
-#else
- int fFlags = fcntl(hSocket, F_GETFL, 0);
- if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == -1)
-#endif
- {
- closesocket(hSocket);
- return false;
- }
+ // Set to non-blocking
+ if (!SetSocketNonBlocking(hSocket, true))
+ return error("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
{
@@ -354,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);
@@ -365,13 +428,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
if (nRet == 0)
{
LogPrint("net", "connection to %s timeout\n", addrConnect.ToString());
- closesocket(hSocket);
+ CloseSocket(hSocket);
return false;
}
if (nRet == SOCKET_ERROR)
{
LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
- closesocket(hSocket);
+ CloseSocket(hSocket);
return false;
}
socklen_t nRetSize = sizeof(nRet);
@@ -382,13 +445,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
#endif
{
LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
- closesocket(hSocket);
+ CloseSocket(hSocket);
return false;
}
if (nRet != 0)
{
LogPrintf("connect() to %s failed after select(): %s\n", addrConnect.ToString(), NetworkErrorString(nRet));
- closesocket(hSocket);
+ CloseSocket(hSocket);
return false;
}
}
@@ -399,26 +462,11 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
#endif
{
LogPrintf("connect() to %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
- closesocket(hSocket);
+ CloseSocket(hSocket);
return false;
}
}
- // this isn't even strictly necessary
- // CNode::ConnectNode immediately turns the socket back to non-blocking
- // but we'll turn it back to blocking just in case
-#ifdef WIN32
- fNonblock = 0;
- if (ioctlsocket(hSocket, FIONBIO, &fNonblock) == SOCKET_ERROR)
-#else
- fFlags = fcntl(hSocket, F_GETFL, 0);
- if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR)
-#endif
- {
- closesocket(hSocket);
- return false;
- }
-
hSocketRet = hSocket;
return true;
}
@@ -880,11 +928,6 @@ uint64_t CNetAddr::GetHash() const
return nRet;
}
-void CNetAddr::print() const
-{
- LogPrintf("CNetAddr(%s)\n", ToString());
-}
-
// private extensions to enum Network, only returned by GetExtNetwork,
// and only used in GetReachabilityFrom
static const int NET_UNKNOWN = NET_MAX + 0;
@@ -1113,11 +1156,6 @@ std::string CService::ToString() const
return ToStringIPPort();
}
-void CService::print() const
-{
- LogPrintf("CService(%s)\n", ToString());
-}
-
void CService::SetPort(unsigned short portIn)
{
port = portIn;
@@ -1258,3 +1296,45 @@ std::string NetworkErrorString(int err)
return strprintf("%s (%d)", s, err);
}
#endif
+
+bool CloseSocket(SOCKET& hSocket)
+{
+ if (hSocket == INVALID_SOCKET)
+ return false;
+#ifdef WIN32
+ int ret = closesocket(hSocket);
+#else
+ int ret = close(hSocket);
+#endif
+ hSocket = INVALID_SOCKET;
+ return ret != SOCKET_ERROR;
+}
+
+bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking)
+{
+ if (fNonBlocking) {
+#ifdef WIN32
+ u_long nOne = 1;
+ if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) {
+#else
+ int fFlags = fcntl(hSocket, F_GETFL, 0);
+ if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) {
+#endif
+ CloseSocket(hSocket);
+ return false;
+ }
+ } else {
+#ifdef WIN32
+ u_long nZero = 0;
+ if (ioctlsocket(hSocket, FIONBIO, &nZero) == SOCKET_ERROR) {
+#else
+ int fFlags = fcntl(hSocket, F_GETFL, 0);
+ if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) {
+#endif
+ CloseSocket(hSocket);
+ return false;
+ }
+ }
+
+ return true;
+}