// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef TRIANGLES_NET_H #define TRIANGLES_NET_H #include #include #include #ifndef WIN32 #include #endif #include "mruset.h" #include "netbase.h" #include "protocol.h" #include "addrman.h" class CNode; class CBlockIndex; bool IsInitialBlockDownload(); void ThreadForkDetector(void*); extern int nBestHeight; extern int nForkAlertCount; inline unsigned int ReceiveFloodSize() { return 50 * 1024 * 1024; } // 50 MB (reduced for Tor-only network) inline unsigned int SendBufferSize() { return 32 * 1024 * 1024; } // 32 MB void AddOneShot(std::string strDest); bool RecvLine(SOCKET hSocket, std::string& strLine); bool GetMyExternalIP(CNetAddr& ipRet); void AddressCurrentlyConnected(const CService& addr); CNode* FindNode(const CNetAddr& ip); CNode* FindNode(const CService& ip); CNode* ConnectNode(CAddress addrConnect, const char *strDest = nullptr); // Adopt a connected I2P SAM data socket as an inbound peer (called from i2p.cpp). void AddI2PInboundNode(SOCKET hSocket, const CAddress& addr); void MapPort(); unsigned short GetListenPort(); bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string())); void StartNode(void* parg); void StartTor(void* parg); bool StopNode(); void SocketSendData(CNode *pnode); enum { LOCAL_NONE, // unknown LOCAL_IF, // address a local interface listens on LOCAL_BIND, // address explicit bound to LOCAL_UPNP, // address reported by UPnP LOCAL_IRC, // address reported by IRC (deprecated) LOCAL_HTTP, // address reported by whatismyip.com and similar LOCAL_MANUAL, // address explicitly specified (-externalip=) LOCAL_MAX }; void SetLimited(enum Network net, bool fLimited = true); bool IsLimited(enum Network net); bool IsLimited(const CNetAddr& addr); bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); bool IsReachable(const CNetAddr &addr); void SetReachable(enum Network net, bool fFlag = true); CAddress GetLocalAddress(const CNetAddr *paddrPeer = nullptr); enum { MSG_TX = 1, MSG_BLOCK, }; /** Thread types */ enum threadId { THREAD_SOCKETHANDLER, THREAD_OPENCONNECTIONS, THREAD_MESSAGEHANDLER, THREAD_RPCLISTENER, THREAD_UPNP, THREAD_HTTPSEED, THREAD_ADDEDCONNECTIONS, THREAD_DUMPADDRESS, THREAD_RPCHANDLER, THREAD_STAKE_MINER, THREAD_MAX }; extern bool fClient; extern bool fDiscover; extern bool fUseUPnP; extern uint64_t nLocalServices; extern uint64_t nLocalHostNonce; extern CAddress addrSeenByPeer; extern std::array vnThreadsRunning; extern CAddrMan addrman; extern std::vector vNodes; extern CCriticalSection cs_vNodes; extern std::map mapRelay; extern std::deque > vRelayExpiration; extern CCriticalSection cs_mapRelay; extern std::map mapAlreadyAskedFor; class CNodeStats { public: uint64_t nServices; int64_t nLastSend; int64_t nLastRecv; uint64_t nSendBytes; uint64_t nRecvBytes; int64_t nTimeConnected; std::string addrName; int nVersion; std::string strSubVer; bool fInbound; int nStartingHeight; int nMisbehavior; int64_t nPingUsecTime; int nBlocksDelivered; int64_t nAvgBlockLatencyUs; }; class CNetMessage { public: bool in_data; // parsing header (false) or data (true) CDataStream hdrbuf; // partially received header CMessageHeader hdr; // complete header unsigned int nHdrPos; CDataStream vRecv; // received message data unsigned int nDataPos; int64_t nTime; // time (in microseconds) of message receipt. CNetMessage(int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), vRecv(nTypeIn, nVersionIn) { hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; nTime = 0; } bool complete() const { if (!in_data) return false; return (hdr.nMessageSize == nDataPos); } void SetVersion(int nVersionIn) { hdrbuf.SetVersion(nVersionIn); vRecv.SetVersion(nVersionIn); } int readHeader(const char *pch, unsigned int nBytes); int readData(const char *pch, unsigned int nBytes); }; class SecMsgNode { public: SecMsgNode() { lastSeen = 0; lastMatched = 0; ignoreUntil = 0; nWakeCounter = 0; nPeerId = 0; fEnabled = false; }; ~SecMsgNode() {}; int64_t lastSeen; int64_t lastMatched; int64_t ignoreUntil; uint32_t nWakeCounter; uint32_t nPeerId; bool fEnabled; }; /** Information about a peer */ class CNode { public: // socket uint64_t nServices; SOCKET hSocket; CDataStream ssSend; size_t nSendSize; // total size of all vSendMsg entries size_t nSendOffset; // offset inside the first vSendMsg already sent std::deque vSendMsg; CCriticalSection cs_vSend; std::deque vRecvMsg; CCriticalSection cs_vRecvMsg; int nRecvVersion; int64_t nLastSend; int64_t nLastRecv; uint64_t nSendBytes; uint64_t nRecvBytes; int64_t nLastSendEmpty; int64_t nTimeConnected; CAddress addr; std::string addrName; CService addrLocal; int nVersion; std::string strSubVer; bool fOneShot; bool fClient; bool fInbound; bool fNetworkNode; bool fSuccessfullyConnected; bool fDisconnect; bool fPreferHeaders; // peer requested block announcements via headers (sendheaders) bool fSendCmpct; // peer supports compact block relay (sendcmpct) CSemaphoreGrant grantOutbound; int nRefCount; protected: // Denial-of-service detection/prevention // Key is IP address, value is banned-until-time static std::map setBanned; static CCriticalSection cs_setBanned; int nMisbehavior; public: uint256 hashContinue; CBlockIndex* pindexLastGetBlocksBegin; uint256 hashLastGetBlocksEnd; CBlockIndex* pindexLastGetHeadersBegin; uint256 hashLastGetHeadersEnd; int nStartingHeight; int64_t nLastTipCheck; // last time we asked this peer for chain tip int64_t nLastIbdHeaderRequest; // last time we sent IBD-mode getheaders to this peer (heartbeat throttle) int64_t nAvgBlockLatencyUs; // rolling average block delivery latency (microseconds) int nBlocksDelivered; // count of blocks delivered by this peer int nBestKnownHeight; // highest block height known to this peer (updated from inv/block msgs) int nIncompatibleGetblocks; // count of getblocks with no common blocks (fork detection) // Option C: peer reliability scoring. Higher = more reliable. // Starts at 100 (neutral), grows with successful block delivery, shrinks with // disconnects and unreachable-on-connect. Used by sync manager to prefer // reliable peers for header/block requests and to demote flaky ones. int nReliabilityScore = 100; int nDisconnectCount = 0; // disconnects since startup int nConnectFailures = 0; // host-unreachable on connect attempts int64_t nLastDisconnectTime = 0; // for flapping detection (many disconnects in short window) // BIP 31 ping/pong latency tracking uint64_t nPingNonceSent; // nonce of last ping sent (0 = no outstanding ping) int64_t nPingUsecStart; // microsecond timestamp when last ping was sent int64_t nPingUsecTime; // last measured round-trip time (microseconds), 0 = unknown int nPingRetryCount; // consecutive pings without pong response // flood relay std::vector vAddrToSend; mruset setAddrKnown; bool fGetAddr; int64_t nLastGetaddrTrigger; // last time we sent this peer a discovery round (getaddr+getseederlist+getwalletaddr) int nSignedPeerBonus; // +N reputation when peer completed walletaddr handshake (signed identity) std::set setKnown; uint256 hashCheckpointKnown; // triangles: known sent sync-checkpoint // inventory based relay mruset setInventoryKnown; std::vector vInventoryToSend; CCriticalSection cs_inventory; std::multimap mapAskFor; SecMsgNode smsgData; CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, INIT_PROTO_VERSION), setAddrKnown(5000) { nServices = 0; hSocket = hSocketIn; nRecvVersion = INIT_PROTO_VERSION; nLastSend = 0; nLastRecv = 0; nSendBytes = 0; nRecvBytes = 0; nLastSendEmpty = GetTime(); nTimeConnected = GetTime(); addr = addrIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; nVersion = 0; strSubVer = ""; fOneShot = false; fClient = false; // set by version message fInbound = fInboundIn; fNetworkNode = false; fSuccessfullyConnected = false; fDisconnect = false; fPreferHeaders = false; fSendCmpct = false; nRefCount = 0; nSendSize = 0; nSendOffset = 0; hashContinue = 0; pindexLastGetBlocksBegin = 0; hashLastGetBlocksEnd = 0; pindexLastGetHeadersBegin = 0; hashLastGetHeadersEnd = 0; nStartingHeight = -1; nLastTipCheck = 0; nLastIbdHeaderRequest = 0; nAvgBlockLatencyUs = 0; nBlocksDelivered = 0; nBestKnownHeight = -1; nIncompatibleGetblocks = 0; nPingNonceSent = 0; nPingUsecStart = 0; nPingUsecTime = 0; nPingRetryCount = 0; fGetAddr = false; nLastGetaddrTrigger = 0; nSignedPeerBonus = 0; nMisbehavior = 0; hashCheckpointKnown = 0; setInventoryKnown.max_size(SendBufferSize() / 1000); // Be shy and don't send version until we hear if (hSocket != INVALID_SOCKET && !fInbound) { printf("CNode(): pfrom-addr %s\n", addrName.c_str()); PushVersion(); } } ~CNode() { if (hSocket != INVALID_SOCKET) { closesocket(hSocket); hSocket = INVALID_SOCKET; } } private: CNode(const CNode&); void operator=(const CNode&); public: int GetRefCount() { assert(nRefCount >= 0); return nRefCount; } // requires LOCK(cs_vRecvMsg) unsigned int GetTotalRecvSize() { unsigned int total = 0; for (const CNetMessage &msg : vRecvMsg) total += msg.vRecv.size() + 24; return total; } // requires LOCK(cs_vRecvMsg) bool ReceiveMsgBytes(const char *pch, unsigned int nBytes); // requires LOCK(cs_vRecvMsg) void SetRecvVersion(int nVersionIn) { nRecvVersion = nVersionIn; for (CNetMessage &msg : vRecvMsg) msg.SetVersion(nVersionIn); } CNode* AddRef() { nRefCount++; return this; } void Release() { nRefCount--; } void AddAddressKnown(const CAddress& addr) { setAddrKnown.insert(addr); } void PushAddress(const CAddress& addr) { // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. if (addr.IsValid() && !setAddrKnown.count(addr)) vAddrToSend.push_back(addr); } void AddInventoryKnown(const CInv& inv) { { LOCK(cs_inventory); setInventoryKnown.insert(inv); } } void PushInventory(const CInv& inv) { { LOCK(cs_inventory); if (!setInventoryKnown.count(inv)) vInventoryToSend.push_back(inv); } } void AskFor(const CInv& inv) { // We're using mapAskFor as a priority queue, // the key is the earliest time the request can be sent int64_t& nRequestTime = mapAlreadyAskedFor[inv]; if (fDebugNet) printf("askfor %s %" PRId64 " (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str()); // Make sure not to reuse time indexes to keep things in the same order int64_t nNow = (GetTime() - 1) * 1000000; static int64_t nLastTime; ++nLastTime; nNow = std::max(nNow, nLastTime); nLastTime = nNow; // During IBD, request immediately (no 2-minute retry delay) // Normal operation: each retry is 2 minutes after the last if (nRequestTime > 0 && IsInitialBlockDownload()) nRequestTime = nNow; else nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow); mapAskFor.insert({nRequestTime, inv}); } void BeginMessage(const char* pszCommand) { ENTER_CRITICAL_SECTION(cs_vSend); assert(ssSend.size() == 0); ssSend << CMessageHeader(pszCommand, 0); if (fDebug) printf("sending: %s ", pszCommand); } void AbortMessage() { ssSend.clear(); LEAVE_CRITICAL_SECTION(cs_vSend); if (fDebug) printf("(aborted)\n"); } void EndMessage() { if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0) { printf("dropmessages DROPPING SEND MESSAGE\n"); AbortMessage(); return; } if (ssSend.size() == 0) return; // Set the size unsigned int nSize = ssSend.size() - CMessageHeader::HEADER_SIZE; memcpy((char*)&ssSend[CMessageHeader::MESSAGE_SIZE_OFFSET], &nSize, sizeof(nSize)); // Set the checksum uint256 hash = Hash(ssSend.begin() + CMessageHeader::HEADER_SIZE, ssSend.end()); unsigned int nChecksum = 0; memcpy(&nChecksum, &hash, sizeof(nChecksum)); assert(ssSend.size () >= CMessageHeader::CHECKSUM_OFFSET + sizeof(nChecksum)); memcpy((char*)&ssSend[CMessageHeader::CHECKSUM_OFFSET], &nChecksum, sizeof(nChecksum)); if (fDebug) { printf("(%d bytes)\n", nSize); } std::deque::iterator it = vSendMsg.insert(vSendMsg.end(), CSerializeData()); ssSend.GetAndClear(*it); nSendSize += (*it).size(); // If write queue empty, attempt "optimistic write" if (it == vSendMsg.begin()) SocketSendData(this); LEAVE_CRITICAL_SECTION(cs_vSend); } void PushVersion(); void PushMessage(const char* pszCommand) { try { BeginMessage(pszCommand); EndMessage(); } catch (...) { AbortMessage(); throw; } } template void PushMessage(const char* pszCommand, const T1& a1, const Args&... args) { try { BeginMessage(pszCommand); ssSend << a1; using swallow = int[]; (void)swallow{0, ((void)(ssSend << args), 0)...}; EndMessage(); } catch (...) { AbortMessage(); throw; } } void PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd); void PushGetHeaders(CBlockIndex* pindexBegin, uint256 hashEnd); bool IsSubscribed(unsigned int nChannel); void Subscribe(unsigned int nChannel, unsigned int nHops=0); void CancelSubscribe(unsigned int nChannel); void CloseSocketDisconnect(); void Cleanup(); // Option C: recompute reliability score from current counters. // Call this periodically (e.g. in sync manager tick) to apply the // flapping penalty (5+ disconnects in 5min = extra 50 penalty). int RecomputeReliabilityScore(); // Denial-of-service detection/prevention // The idea is to detect peers that are behaving // badly and disconnect/ban them, but do it in a // one-coding-mistake-won't-shatter-the-entire-network // way. // IMPORTANT: There should be nothing I can give a // node that it will forward on that will make that // node's peers drop it. If there is, an attacker // can isolate a node and/or try to split the network. // Dropping a node for sending stuff that is invalid // now but might be valid in a later version is also // dangerous, because it can cause a network split // between nodes running old code and nodes running // new code. static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); static bool Ban(CNetAddr ip, int64_t banTime); static bool Unban(CNetAddr ip); static void GetBanned(std::map& mapBannedOut); bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot void copyStats(CNodeStats &stats); }; inline void RelayInventory(const CInv& inv) { // Put on lists to offer to the other nodes { LOCK(cs_vNodes); for (CNode* pnode : vNodes) pnode->PushInventory(inv); } } class CTransaction; void RelayTransaction(const CTransaction& tx, const uint256& hash); void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataStream& ss); #endif