Merge #15824: docs: Improve netbase comments

c7f6ce74d3 docs: Improve netbase comments (Carl Dong)

Pull request description:

  Second in a series of PRs documenting the net stack. Contributed with sincere thanks to sipa, laanwj, and gmaxwell for providing much of the history, context, and rationale.

ACKs for top commit:
  laanwj:
    ACK c7f6ce74d3

Tree-SHA512: ad83054d3b8d0c8c3fb55be011bcf294176e7509513bf61326866afd53e8159644e0d59bb3a2f404717f525cbf736096d4c1990e61cfd89845d51fa6b5394b7c
pull/16402/head
Wladimir J. van der Laan 5 years ago
commit 6d37ed888e
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D

@ -65,6 +65,12 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
{ {
CNetAddr addr; CNetAddr addr;
// From our perspective, onion addresses are not hostnames but rather
// direct encodings of CNetAddr much like IPv4 dotted-decimal notation
// or IPv6 colon-separated hextet notation. Since we can't use
// getaddrinfo to decode them and it wouldn't make sense to resolve
// them, we return a network address representing it instead. See
// CNetAddr::SetSpecial(const std::string&) for more details.
if (addr.SetSpecial(std::string(pszName))) { if (addr.SetSpecial(std::string(pszName))) {
vIP.push_back(addr); vIP.push_back(addr);
return true; return true;
@ -74,15 +80,25 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
struct addrinfo aiHint; struct addrinfo aiHint;
memset(&aiHint, 0, sizeof(struct addrinfo)); memset(&aiHint, 0, sizeof(struct addrinfo));
// We want a TCP port, which is a streaming socket type
aiHint.ai_socktype = SOCK_STREAM; aiHint.ai_socktype = SOCK_STREAM;
aiHint.ai_protocol = IPPROTO_TCP; aiHint.ai_protocol = IPPROTO_TCP;
// We don't care which address family (IPv4 or IPv6) is returned
aiHint.ai_family = AF_UNSPEC; aiHint.ai_family = AF_UNSPEC;
// If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only
// return addresses whose family we have an address configured for.
//
// If we don't allow lookups, then use the AI_NUMERICHOST flag for
// getaddrinfo to only decode numerical network addresses and suppress
// hostname lookups.
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
struct addrinfo *aiRes = nullptr; struct addrinfo *aiRes = nullptr;
int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes); int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
if (nErr) if (nErr)
return false; return false;
// Traverse the linked list starting with aiTrav, add all non-internal
// IPv4,v6 addresses to vIP while respecting nMaxSolutions.
struct addrinfo *aiTrav = aiRes; struct addrinfo *aiTrav = aiRes;
while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
{ {
@ -112,6 +128,21 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
return (vIP.size() > 0); return (vIP.size() > 0);
} }
/**
* Resolve a host string to its corresponding network addresses.
*
* @param pszName The string representing a host. Could be a name or a numerical
* IP address (IPv6 addresses in their bracketed form are
* allowed).
* @param[out] vIP The resulting network addresses to which the specified host
* string resolved.
*
* @returns Whether or not the specified host string successfully resolved to
* any resulting network addresses.
*
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
* for additional parameter descriptions.
*/
bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup) bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
{ {
std::string strHost(pszName); std::string strHost(pszName);
@ -124,6 +155,12 @@ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nM
return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup); return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup);
} }
/**
* Resolve a host string to its first corresponding network address.
*
* @see LookupHost(const char *, std::vector<CNetAddr>&, unsigned int, bool) for
* additional parameter descriptions.
*/
bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup) bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
{ {
std::vector<CNetAddr> vIP; std::vector<CNetAddr> vIP;
@ -134,6 +171,26 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
return true; return true;
} }
/**
* Resolve a service string to its corresponding service.
*
* @param pszName The string representing a service. Could be a name or a
* numerical IP address (IPv6 addresses should be in their
* disambiguated bracketed form), optionally followed by a port
* number. (e.g. example.com:8333 or
* [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420)
* @param[out] vAddr The resulting services to which the specified service string
* resolved.
* @param portDefault The default port for resulting services if not specified
* by the service string.
* @param fAllowLookup Whether or not hostname lookups are permitted. If yes,
* external queries may be performed.
* @param nMaxSolutions The maximum number of results we want, specifying 0
* means "as many solutions as we get."
*
* @returns Whether or not the service string successfully resolved to any
* resulting services.
*/
bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions) bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions)
{ {
if (pszName[0] == 0) if (pszName[0] == 0)
@ -152,6 +209,12 @@ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault,
return true; return true;
} }
/**
* Resolve a service string to its first corresponding service.
*
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
* for additional parameter descriptions.
*/
bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup) bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup)
{ {
std::vector<CService> vService; std::vector<CService> vService;
@ -162,6 +225,16 @@ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLoo
return true; return true;
} }
/**
* Resolve a service string with a numeric IP to its first corresponding
* service.
*
* @returns The resulting CService if the resolution was successful, [::]:0
* otherwise.
*
* @see Lookup(const char *, CService&, int, bool) for additional parameter
* descriptions.
*/
CService LookupNumeric(const char *pszName, int portDefault) CService LookupNumeric(const char *pszName, int portDefault)
{ {
CService addr; CService addr;
@ -231,22 +304,29 @@ enum class IntrRecvError {
}; };
/** /**
* Read bytes from socket. This will either read the full number of bytes requested * Try to read a specified number of bytes from a socket. Please read the "see
* or return False on error or timeout. * also" section for more detail.
* This function can be interrupted by calling InterruptSocks5()
* *
* @param data Buffer to receive into * @param data The buffer where the read bytes should be stored.
* @param len Length of data to receive * @param len The number of bytes to read into the specified buffer.
* @param timeout Timeout in milliseconds for receive operation * @param timeout The total timeout in milliseconds for this read.
* @param hSocket The socket (has to be in non-blocking mode) from which to read
* bytes.
* *
* @note This function requires that hSocket is in non-blocking mode. * @returns An IntrRecvError indicating the resulting status of this read.
* IntrRecvError::OK only if all of the specified number of bytes were
* read.
*
* @see This function can be interrupted by calling InterruptSocks5(bool).
* Sockets can be made non-blocking with SetSocketNonBlocking(const
* SOCKET&, bool).
*/ */
static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket) static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket)
{ {
int64_t curTime = GetTimeMillis(); int64_t curTime = GetTimeMillis();
int64_t endTime = curTime + timeout; int64_t endTime = curTime + timeout;
// Maximum time to wait in one select call. It will take up until this time (in millis) // Maximum time to wait for I/O readiness. It will take up until this time
// to break off in case of an interruption. // (in millis) to break off in case of an interruption.
const int64_t maxWait = 1000; const int64_t maxWait = 1000;
while (len > 0 && curTime < endTime) { while (len > 0 && curTime < endTime) {
ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first
@ -261,6 +341,8 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c
if (!IsSelectableSocket(hSocket)) { if (!IsSelectableSocket(hSocket)) {
return IntrRecvError::NetworkError; return IntrRecvError::NetworkError;
} }
// Only wait at most maxWait milliseconds at a time, unless
// we're approaching the end of the specified total timeout
int timeout_ms = std::min(endTime - curTime, maxWait); int timeout_ms = std::min(endTime - curTime, maxWait);
#ifdef USE_POLL #ifdef USE_POLL
struct pollfd pollfd = {}; struct pollfd pollfd = {};
@ -320,7 +402,24 @@ static std::string Socks5ErrorString(uint8_t err)
} }
} }
/** Connect using SOCKS5 (as described in RFC1928) */ /**
* Connect to a specified destination service through an already connected
* SOCKS5 proxy.
*
* @param strDest The destination fully-qualified domain name.
* @param port The destination port.
* @param auth The credentials with which to authenticate with the specified
* SOCKS5 proxy.
* @param hSocket The SOCKS5 proxy socket.
*
* @returns Whether or not the operation succeeded.
*
* @note The specified SOCKS5 proxy socket must already be connected to the
* SOCKS5 proxy.
*
* @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol
* Version 5</a>
*/
static bool Socks5(const std::string& strDest, int port, const ProxyCredentials *auth, const SOCKET& hSocket) static bool Socks5(const std::string& strDest, int port, const ProxyCredentials *auth, const SOCKET& hSocket)
{ {
IntrRecvError recvr; IntrRecvError recvr;
@ -328,15 +427,15 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
if (strDest.size() > 255) { if (strDest.size() > 255) {
return error("Hostname too long"); return error("Hostname too long");
} }
// Accepted authentication methods // Construct the version identifier/method selection message
std::vector<uint8_t> vSocks5Init; std::vector<uint8_t> vSocks5Init;
vSocks5Init.push_back(SOCKSVersion::SOCKS5); vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
if (auth) { if (auth) {
vSocks5Init.push_back(0x02); // Number of methods vSocks5Init.push_back(0x02); // 2 method identifiers follow...
vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::NOAUTH);
vSocks5Init.push_back(SOCKS5Method::USER_PASS); vSocks5Init.push_back(SOCKS5Method::USER_PASS);
} else { } else {
vSocks5Init.push_back(0x01); // Number of methods vSocks5Init.push_back(0x01); // 1 method identifier follows...
vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::NOAUTH);
} }
ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
@ -440,8 +539,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return true; return true;
} }
/**
* Try to create a socket file descriptor with specific properties in the
* communications domain (address family) of the specified service.
*
* For details on the desired properties, see the inline comments in the source
* code.
*/
SOCKET CreateSocket(const CService &addrConnect) SOCKET CreateSocket(const CService &addrConnect)
{ {
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr; struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr); socklen_t len = sizeof(sockaddr);
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
@ -449,10 +556,13 @@ SOCKET CreateSocket(const CService &addrConnect)
return INVALID_SOCKET; return INVALID_SOCKET;
} }
// Create a TCP socket in the address family of the specified service.
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET) if (hSocket == INVALID_SOCKET)
return INVALID_SOCKET; return INVALID_SOCKET;
// Ensure that waiting for I/O on this socket won't result in undefined
// behavior.
if (!IsSelectableSocket(hSocket)) { if (!IsSelectableSocket(hSocket)) {
CloseSocket(hSocket); CloseSocket(hSocket);
LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n"); LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n");
@ -461,17 +571,18 @@ SOCKET CreateSocket(const CService &addrConnect)
#ifdef SO_NOSIGPIPE #ifdef SO_NOSIGPIPE
int set = 1; int set = 1;
// Different way of disabling SIGPIPE on BSD // Set the no-sigpipe option on the socket for BSD systems, other UNIXes
// should use the MSG_NOSIGNAL flag for every send.
setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));
#endif #endif
//Disable Nagle's algorithm // Set the no-delay option (disable Nagle's algorithm) on the TCP socket.
SetSocketNoDelay(hSocket); SetSocketNoDelay(hSocket);
// Set to non-blocking // Set the non-blocking option on the socket.
if (!SetSocketNonBlocking(hSocket, true)) { if (!SetSocketNonBlocking(hSocket, true)) {
CloseSocket(hSocket); CloseSocket(hSocket);
LogPrintf("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); LogPrintf("CreateSocket: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
} }
return hSocket; return hSocket;
} }
@ -486,8 +597,21 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg
} }
} }
/**
* Try to connect to the specified service on the specified socket.
*
* @param addrConnect The service to which to connect.
* @param hSocket The socket on which to connect.
* @param nTimeout Wait this many milliseconds for the connection to be
* established.
* @param manual_connection Whether or not the connection was manually requested
* (e.g. thru the addnode RPC)
*
* @returns Whether or not a connection was successfully made.
*/
bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection) bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection)
{ {
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr; struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr); socklen_t len = sizeof(sockaddr);
if (hSocket == INVALID_SOCKET) { if (hSocket == INVALID_SOCKET) {
@ -498,12 +622,17 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString());
return false; return false;
} }
// Connect to the addrConnect service on the hSocket socket.
if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
{ {
int nErr = WSAGetLastError(); int nErr = WSAGetLastError();
// WSAEINVAL is here because some legacy version of winsock uses it // WSAEINVAL is here because some legacy version of winsock uses it
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL)
{ {
// Connection didn't actually fail, but is being established
// asynchronously. Thus, use async I/O api (select/poll)
// synchronously to check for successful connection with a timeout.
#ifdef USE_POLL #ifdef USE_POLL
struct pollfd pollfd = {}; struct pollfd pollfd = {};
pollfd.fd = hSocket; pollfd.fd = hSocket;
@ -516,6 +645,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
FD_SET(hSocket, &fdset); FD_SET(hSocket, &fdset);
int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
#endif #endif
// Upon successful completion, both select and poll return the total
// number of file descriptors that have been selected. A value of 0
// indicates that the call timed out and no file descriptors have
// been selected.
if (nRet == 0) if (nRet == 0)
{ {
LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString());
@ -526,6 +659,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
return false; return false;
} }
// Even if the select/poll was successful, the connect might not
// have been successful. The reason for this failure is hidden away
// in the SO_ERROR for the socket in modern systems. We read it into
// nRet here.
socklen_t nRetSize = sizeof(nRet); socklen_t nRetSize = sizeof(nRet);
if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR) if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR)
{ {
@ -569,6 +707,22 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) {
return true; return true;
} }
/**
* Set the name proxy to use for all connections to nodes specified by a
* hostname. After setting this proxy, connecting to a node sepcified by a
* hostname won't result in a local lookup of said hostname, rather, connect to
* the node by asking the name proxy for a proxy connection to the hostname,
* effectively delegating the hostname lookup to the specified proxy.
*
* This delegation increases privacy for those who set the name proxy as they no
* longer leak their external hostname queries to their DNS servers.
*
* @returns Whether or not the operation succeeded.
*
* @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5
* server in common use (most notably Tor) actually implements UDP
* support, and a DNS resolver is beyond the scope of this project.
*/
bool SetNameProxy(const proxyType &addrProxy) { bool SetNameProxy(const proxyType &addrProxy) {
if (!addrProxy.IsValid()) if (!addrProxy.IsValid())
return false; return false;
@ -599,6 +753,21 @@ bool IsProxy(const CNetAddr &addr) {
return false; return false;
} }
/**
* Connect to a specified destination service through a SOCKS5 proxy by first
* connecting to the SOCKS5 proxy.
*
* @param proxy The SOCKS5 proxy.
* @param strDest The destination service to which to connect.
* @param port The destination port.
* @param hSocket The socket on which to connect to the SOCKS5 proxy.
* @param nTimeout Wait this many milliseconds for the connection to the SOCKS5
* proxy to be established.
* @param outProxyConnectionFailed[out] Whether or not the connection to the
* SOCKS5 proxy failed.
*
* @returns Whether or not the operation succeeded.
*/
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed) bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed)
{ {
// first connect to proxy server // first connect to proxy server
@ -623,6 +792,17 @@ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int
return true; return true;
} }
/**
* Parse and resolve a specified subnet string into the appropriate internal
* representation.
*
* @param pszName A string representation of a subnet of the form `network
* address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g.
* `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`).
* @param ret The resulting internal representation of a subnet.
*
* @returns Whether the operation succeeded or not.
*/
bool LookupSubNet(const char* pszName, CSubNet& ret) bool LookupSubNet(const char* pszName, CSubNet& ret)
{ {
std::string strSubnet(pszName); std::string strSubnet(pszName);
@ -630,6 +810,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret)
std::vector<CNetAddr> vIP; std::vector<CNetAddr> vIP;
std::string strAddress = strSubnet.substr(0, slash); std::string strAddress = strSubnet.substr(0, slash);
// TODO: Use LookupHost(const char *, CNetAddr&, bool) instead to just get
// one CNetAddr.
if (LookupHost(strAddress.c_str(), vIP, 1, false)) if (LookupHost(strAddress.c_str(), vIP, 1, false))
{ {
CNetAddr network = vIP[0]; CNetAddr network = vIP[0];
@ -637,8 +819,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret)
{ {
std::string strNetmask = strSubnet.substr(slash + 1); std::string strNetmask = strSubnet.substr(slash + 1);
int32_t n; int32_t n;
// IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n if (ParseInt32(strNetmask, &n)) {
if (ParseInt32(strNetmask, &n)) { // If valid number, assume /24 syntax // If valid number, assume CIDR variable-length subnet masking
ret = CSubNet(network, n); ret = CSubNet(network, n);
return ret.IsValid(); return ret.IsValid();
} }

Loading…
Cancel
Save