diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp index f3e5d46b54..11d0906a42 100644 --- a/src/seeder/bitcoin.cpp +++ b/src/seeder/bitcoin.cpp @@ -1,348 +1,354 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include // Weither we are on testnet or mainnet. bool fTestNet; // The network magic to use. CMessageHeader::MessageMagic netMagic = {{0xe3, 0xe1, 0xf3, 0xe8}}; #define BITCOIN_SEED_NONCE 0x0539a019ca550825ULL static const uint32_t allones(-1); void CSeederNode::BeginMessage(const char *pszCommand) { if (nHeaderStart != allones) { AbortMessage(); } nHeaderStart = vSend.size(); vSend << CMessageHeader(netMagic, pszCommand, 0); nMessageStart = vSend.size(); // fprintf(stdout, "%s: SEND %s\n", ToString(you).c_str(), pszCommand); } void CSeederNode::AbortMessage() { if (nHeaderStart == allones) { return; } vSend.resize(nHeaderStart); nHeaderStart = allones; nMessageStart = allones; } void CSeederNode::EndMessage() { if (nHeaderStart == allones) { return; } uint32_t nSize = vSend.size() - nMessageStart; memcpy((char *)&vSend[nHeaderStart] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize)); if (vSend.GetVersion() >= 209) { uint256 hash = Hash(vSend.begin() + nMessageStart, vSend.end()); unsigned int nChecksum = 0; memcpy(&nChecksum, &hash, sizeof(nChecksum)); assert(nMessageStart - nHeaderStart >= offsetof(CMessageHeader, pchChecksum) + sizeof(nChecksum)); memcpy((char *)&vSend[nHeaderStart] + offsetof(CMessageHeader, pchChecksum), &nChecksum, sizeof(nChecksum)); } nHeaderStart = allones; nMessageStart = allones; } void CSeederNode::Send() { if (sock == INVALID_SOCKET) { return; } if (vSend.empty()) { return; } int nBytes = send(sock, &vSend[0], vSend.size(), 0); if (nBytes > 0) { vSend.erase(vSend.begin(), vSend.begin() + nBytes); } else { close(sock); sock = INVALID_SOCKET; } } void CSeederNode::PushVersion() { int64_t nTime = time(nullptr); uint64_t nLocalNonce = BITCOIN_SEED_NONCE; int64_t nLocalServices = 0; CService myService; CAddress me(myService, ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH)); BeginMessage("version"); int nBestHeight = GetRequireHeight(); std::string ver = "/bitcoin-cash-seeder:0.15/"; vSend << PROTOCOL_VERSION << nLocalServices << nTime << you << me << nLocalNonce << ver << nBestHeight; EndMessage(); } void CSeederNode::GotVersion() { // fprintf(stdout, "\n%s: version %i\n", ToString(you).c_str(), // nVersion); if (vAddr) { BeginMessage("getaddr"); EndMessage(); doneAfter = time(nullptr) + GetTimeout(); } else { doneAfter = time(nullptr) + 1; } } bool CSeederNode::ProcessMessage(std::string strCommand, CDataStream &recv) { // fprintf(stdout, "%s: RECV %s\n", ToString(you).c_str(), // strCommand.c_str()); if (strCommand == "version") { int64_t nTime; CAddress addrMe; CAddress addrFrom; uint64_t nNonce = 1; uint64_t nServiceInt; recv >> nVersion >> nServiceInt >> nTime >> addrMe; you.nServices = ServiceFlags(nServiceInt); recv >> addrFrom >> nNonce; recv >> strSubVer; recv >> nStartingHeight; BeginMessage("verack"); EndMessage(); vSend.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); return false; } if (strCommand == "verack") { vRecv.SetVersion(std::min(nVersion, PROTOCOL_VERSION)); GotVersion(); return false; } if (strCommand == "addr" && vAddr) { std::vector vAddrNew; recv >> vAddrNew; // fprintf(stdout, "%s: got %i addresses\n", ToString(you).c_str(), // (int)vAddrNew.size()); int64_t now = time(nullptr); std::vector::iterator it = vAddrNew.begin(); if (vAddrNew.size() > 1) { - if (doneAfter == 0 || doneAfter > now + 1) doneAfter = now + 1; + if (doneAfter == 0 || doneAfter > now + 1) { + doneAfter = now + 1; + } } while (it != vAddrNew.end()) { CAddress &addr = *it; // fprintf(stdout, "%s: got address %s\n", // ToString(you).c_str(), // addr.ToString().c_str(), (int)(vAddr->size())); it++; if (addr.nTime <= 100000000 || addr.nTime > now + 600) { addr.nTime = now - 5 * 86400; } if (addr.nTime > now - 604800) { vAddr->push_back(addr); } // fprintf(stdout, "%s: added address %s (#%i)\n", // ToString(you).c_str(), // addr.ToString().c_str(), (int)(vAddr->size())); if (vAddr->size() > 1000) { doneAfter = 1; return true; } } return false; } return false; } bool CSeederNode::ProcessMessages() { if (vRecv.empty()) { return false; } do { CDataStream::iterator pstart = std::search( vRecv.begin(), vRecv.end(), BEGIN(netMagic), END(netMagic)); uint32_t nHeaderSize = GetSerializeSize(CMessageHeader(netMagic), vRecv.GetVersion()); if (vRecv.end() - pstart < nHeaderSize) { if (vRecv.size() > nHeaderSize) { vRecv.erase(vRecv.begin(), vRecv.end() - nHeaderSize); } break; } vRecv.erase(vRecv.begin(), pstart); std::vector vHeaderSave(vRecv.begin(), vRecv.begin() + nHeaderSize); CMessageHeader hdr(netMagic); vRecv >> hdr; if (!hdr.IsValidWithoutConfig(netMagic)) { // fprintf(stdout, "%s: BAD (invalid header)\n", // ToString(you).c_str()); ban = 100000; return true; } std::string strCommand = hdr.GetCommand(); unsigned int nMessageSize = hdr.nMessageSize; if (nMessageSize > MAX_SIZE) { // fprintf(stdout, "%s: BAD (message too large)\n", // ToString(you).c_str()); ban = 100000; return true; } if (nMessageSize > vRecv.size()) { vRecv.insert(vRecv.begin(), vHeaderSave.begin(), vHeaderSave.end()); break; } if (vRecv.GetVersion() >= 209) { uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize); if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { continue; } } CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.GetType(), vRecv.GetVersion()); vRecv.ignore(nMessageSize); if (ProcessMessage(strCommand, vMsg)) { return true; } // fprintf(stdout, "%s: done processing %s\n", // ToString(you).c_str(), // strCommand.c_str()); } while (1); return false; } CSeederNode::CSeederNode(const CService &ip, std::vector *vAddrIn) : sock(INVALID_SOCKET), vSend(SER_NETWORK, 0), vRecv(SER_NETWORK, 0), nHeaderStart(-1), nMessageStart(-1), nVersion(0), vAddr(vAddrIn), ban(0), doneAfter(0), you(ip, ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH)) { if (time(nullptr) > 1329696000) { vSend.SetVersion(209); vRecv.SetVersion(209); } } bool CSeederNode::Run() { // FIXME: This logic is duplicated with CConnman::ConnectNode for no // good reason. bool connected = false; proxyType proxy; if (you.IsValid()) { bool proxyConnectionFailed = false; if (GetProxy(you.GetNetwork(), proxy)) { sock = CreateSocket(proxy.proxy); if (sock == INVALID_SOCKET) { return false; } connected = ConnectThroughProxy( proxy, you.ToStringIP(), you.GetPort(), sock, nConnectTimeout, &proxyConnectionFailed); } else { // no proxy needed (none set for target network) sock = CreateSocket(you); if (sock == INVALID_SOCKET) { return false; } // no proxy needed (none set for target network) connected = ConnectSocketDirectly(you, sock, nConnectTimeout, true); } } if (!connected) { // fprintf(stdout, "Cannot connect to %s\n", ToString(you).c_str()); CloseSocket(sock); return false; } PushVersion(); Send(); bool res = true; int64_t now; while (now = time(nullptr), ban == 0 && (doneAfter == 0 || doneAfter > now) && sock != INVALID_SOCKET) { char pchBuf[0x10000]; fd_set fdsetRecv; fd_set fdsetError; FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetError); FD_SET(sock, &fdsetRecv); FD_SET(sock, &fdsetError); struct timeval wa; if (doneAfter) { wa.tv_sec = doneAfter - now; wa.tv_usec = 0; } else { wa.tv_sec = GetTimeout(); wa.tv_usec = 0; } int ret = select(sock + 1, &fdsetRecv, nullptr, &fdsetError, &wa); if (ret != 1) { - if (!doneAfter) res = false; + if (!doneAfter) { + res = false; + } break; } int nBytes = recv(sock, pchBuf, sizeof(pchBuf), 0); int nPos = vRecv.size(); if (nBytes > 0) { vRecv.resize(nPos + nBytes); memcpy(&vRecv[nPos], pchBuf, nBytes); } else if (nBytes == 0) { // fprintf(stdout, "%s: BAD (connection closed prematurely)\n", // ToString(you).c_str()); res = false; break; } else { // fprintf(stdout, "%s: BAD (connection error)\n", // ToString(you).c_str()); res = false; break; } ProcessMessages(); Send(); } - if (sock == INVALID_SOCKET) res = false; + if (sock == INVALID_SOCKET) { + res = false; + } close(sock); sock = INVALID_SOCKET; return (ban == 0) && res; } bool TestNode(const CService &cip, int &ban, int &clientV, std::string &clientSV, int &blocks, std::vector *vAddr) { try { CSeederNode node(cip, vAddr); bool ret = node.Run(); if (!ret) { ban = node.GetBan(); } else { ban = 0; } clientV = node.GetClientVersion(); clientSV = node.GetClientSubVersion(); blocks = node.GetStartingHeight(); // fprintf(stdout, "%s: %s!!!\n", cip.ToString().c_str(), ret ? "GOOD" : // "BAD"); return ret; } catch (std::ios_base::failure &e) { ban = 0; return false; } } diff --git a/src/seeder/db.cpp b/src/seeder/db.cpp index f96893e61c..8a1f3ffeed 100644 --- a/src/seeder/db.cpp +++ b/src/seeder/db.cpp @@ -1,232 +1,243 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include void CAddrInfo::Update(bool good) { int64_t now = time(nullptr); - if (ourLastTry == 0) ourLastTry = now - MIN_RETRY; + if (ourLastTry == 0) { + ourLastTry = now - MIN_RETRY; + } int age = now - ourLastTry; lastTry = now; ourLastTry = now; total++; if (good) { success++; ourLastSuccess = now; } stat2H.Update(good, age, 3600 * 2); stat8H.Update(good, age, 3600 * 8); stat1D.Update(good, age, 3600 * 24); stat1W.Update(good, age, 3600 * 24 * 7); stat1M.Update(good, age, 3600 * 24 * 30); int64_t ign = GetIgnoreTime(); - if (ign && (ignoreTill == 0 || ignoreTill < ign + now)) + if (ign && (ignoreTill == 0 || ignoreTill < ign + now)) { ignoreTill = ign + now; + } // fprintf(stdout, "%s: got %s result: success=%i/%i; // 2H:%.2f%%-%.2f%%(%.2f) 8H:%.2f%%-%.2f%%(%.2f) 1D:%.2f%%-%.2f%%(%.2f) // 1W:%.2f%%-%.2f%%(%.2f) \n", ToString(ip).c_str(), good ? "good" : "bad", // success, total, 100.0 * stat2H.reliability, 100.0 * (stat2H.reliability // + 1.0 - stat2H.weight), stat2H.count, 100.0 * stat8H.reliability, 100.0 // * (stat8H.reliability + 1.0 - stat8H.weight), stat8H.count, 100.0 * // stat1D.reliability, 100.0 * (stat1D.reliability + 1.0 - stat1D.weight), // stat1D.count, 100.0 * stat1W.reliability, 100.0 * (stat1W.reliability // + 1.0 - stat1W.weight), stat1W.count); } bool CAddrDb::Get_(CServiceResult &ip, int &wait) { int64_t now = time(nullptr); size_t tot = unkId.size() + ourId.size(); if (tot == 0) { wait = 5; return false; } do { size_t rnd = rand() % tot; int ret; if (rnd < unkId.size()) { std::set::iterator it = unkId.end(); it--; ret = *it; unkId.erase(it); } else { ret = ourId.front(); if (time(nullptr) - idToInfo[ret].ourLastTry < MIN_RETRY) { return false; } ourId.pop_front(); } if (idToInfo[ret].ignoreTill && idToInfo[ret].ignoreTill < now) { ourId.push_back(ret); idToInfo[ret].ourLastTry = now; } else { ip.service = idToInfo[ret].ip; ip.ourLastSuccess = idToInfo[ret].ourLastSuccess; break; } } while (1); nDirty++; return true; } int CAddrDb::Lookup_(const CService &ip) { - if (ipToId.count(ip)) return ipToId[ip]; + if (ipToId.count(ip)) { + return ipToId[ip]; + } return -1; } void CAddrDb::Good_(const CService &addr, int clientV, std::string clientSV, int blocks) { int id = Lookup_(addr); - if (id == -1) return; + if (id == -1) { + return; + } unkId.erase(id); banned.erase(addr); CAddrInfo &info = idToInfo[id]; info.clientVersion = clientV; info.clientSubVersion = clientSV; info.blocks = blocks; info.Update(true); if (info.IsGood() && goodId.count(id) == 0) { goodId.insert(id); // fprintf(stdout, "%s: good; %i good nodes now\n", // ToString(addr).c_str(), (int)goodId.size()); } nDirty++; ourId.push_back(id); } void CAddrDb::Bad_(const CService &addr, int ban) { int id = Lookup_(addr); - if (id == -1) return; + if (id == -1) { + return; + } unkId.erase(id); CAddrInfo &info = idToInfo[id]; info.Update(false); uint32_t now = time(nullptr); int ter = info.GetBanTime(); if (ter) { // fprintf(stdout, "%s: terrible\n", ToString(addr).c_str()); if (ban < ter) ban = ter; } if (ban > 0) { // fprintf(stdout, "%s: ban for %i seconds\n", // ToString(addr).c_str(), ban); banned[info.ip] = ban + now; ipToId.erase(info.ip); goodId.erase(id); idToInfo.erase(id); } else { if (/*!info.IsGood() && */ goodId.count(id) == 1) { goodId.erase(id); // fprintf(stdout, "%s: not good; %i good nodes left\n", // ToString(addr).c_str(), (int)goodId.size()); } ourId.push_back(id); } nDirty++; } void CAddrDb::Skipped_(const CService &addr) { int id = Lookup_(addr); - if (id == -1) return; + if (id == -1) { + return; + } unkId.erase(id); ourId.push_back(id); // fprintf(stdout, "%s: skipped\n", ToString(addr).c_str()); nDirty++; } void CAddrDb::Add_(const CAddress &addr, bool force) { if (!force && !addr.IsRoutable()) { return; } CService ipp(addr); if (banned.count(ipp)) { time_t bantime = banned[ipp]; if (force || (bantime < time(nullptr) && addr.nTime > bantime)) { banned.erase(ipp); } else { return; } } if (ipToId.count(ipp)) { CAddrInfo &ai = idToInfo[ipToId[ipp]]; if (addr.nTime > ai.lastTry || ai.services != addr.nServices) { ai.lastTry = addr.nTime; ai.services |= addr.nServices; // fprintf(stdout, "%s: updated\n", ToString(addr).c_str()); } if (force) { ai.ignoreTill = 0; } return; } CAddrInfo ai; ai.ip = ipp; ai.services = addr.nServices; ai.lastTry = addr.nTime; ai.ourLastTry = 0; ai.total = 0; ai.success = 0; int id = nId++; idToInfo[id] = ai; ipToId[ipp] = id; // fprintf(stdout, "%s: added\n", ToString(ipp).c_str(), ipToId[ipp]); unkId.insert(id); nDirty++; } void CAddrDb::GetIPs_(std::set &ips, uint64_t requestedFlags, uint32_t max, const bool *nets) { if (goodId.size() == 0) { int id = -1; if (ourId.size() == 0) { if (unkId.size() == 0) { return; } id = *unkId.begin(); } else { id = *ourId.begin(); } if (id >= 0 && (idToInfo[id].services & requestedFlags) == requestedFlags) { ips.insert(idToInfo[id].ip); } return; } std::vector goodIdFiltered; for (auto &id : goodId) { if ((idToInfo[id].services & requestedFlags) == requestedFlags) { goodIdFiltered.push_back(id); } } if (!goodIdFiltered.size()) { return; } if (max > goodIdFiltered.size() / 2) { max = goodIdFiltered.size() / 2; } if (max < 1) { max = 1; } std::set ids; while (ids.size() < max) { ids.insert(goodIdFiltered[rand() % goodIdFiltered.size()]); } for (auto &id : ids) { CService &ip = idToInfo[id].ip; if (nets[ip.GetNetwork()]) { ips.insert(ip); } } } diff --git a/src/seeder/db.h b/src/seeder/db.h index d666ece1c8..f48abc6b4e 100644 --- a/src/seeder/db.h +++ b/src/seeder/db.h @@ -1,452 +1,482 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SEEDER_DB_H #define BITCOIN_SEEDER_DB_H #include #include #include #include #include #include #include #include #include #include #include #include #define MIN_RETRY 1000 #define REQUIRE_VERSION 70001 static inline int GetRequireHeight(const bool testnet = fTestNet) { return testnet ? 500000 : 350000; } static inline std::string ToString(const CService &ip) { std::string str = ip.ToString(); while (str.size() < 22) { str += ' '; } return str; } class CAddrStat { private: float weight; float count; float reliability; public: CAddrStat() : weight(0), count(0), reliability(0) {} void Update(bool good, int64_t age, double tau) { double f = exp(-age / tau); reliability = reliability * f + (good ? (1.0 - f) : 0); count = count * f + 1; weight = weight * f + (1.0 - f); } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(weight); READWRITE(count); READWRITE(reliability); } friend class CAddrInfo; }; class CAddrReport { public: CService ip; int clientVersion; int blocks; double uptime[5]; std::string clientSubVersion; int64_t lastSuccess; bool fGood; uint64_t services; }; class CAddrInfo { private: CService ip; uint64_t services; int64_t lastTry; int64_t ourLastTry; int64_t ourLastSuccess; int64_t ignoreTill; CAddrStat stat2H; CAddrStat stat8H; CAddrStat stat1D; CAddrStat stat1W; CAddrStat stat1M; int clientVersion; int blocks; int total; int success; std::string clientSubVersion; public: CAddrInfo() : services(0), lastTry(0), ourLastTry(0), ourLastSuccess(0), ignoreTill(0), clientVersion(0), blocks(0), total(0), success(0) {} CAddrReport GetReport() const { CAddrReport ret; ret.ip = ip; ret.clientVersion = clientVersion; ret.clientSubVersion = clientSubVersion; ret.blocks = blocks; ret.uptime[0] = stat2H.reliability; ret.uptime[1] = stat8H.reliability; ret.uptime[2] = stat1D.reliability; ret.uptime[3] = stat1W.reliability; ret.uptime[4] = stat1M.reliability; ret.lastSuccess = ourLastSuccess; ret.fGood = IsGood(); ret.services = services; return ret; } bool IsGood() const { - if (ip.GetPort() != GetDefaultPort()) return false; - if (!(services & NODE_NETWORK)) return false; - if (!ip.IsRoutable()) return false; - if (clientVersion && clientVersion < REQUIRE_VERSION) return false; - if (blocks && blocks < GetRequireHeight()) return false; + if (ip.GetPort() != GetDefaultPort()) { + return false; + } + if (!(services & NODE_NETWORK)) { + return false; + } + if (!ip.IsRoutable()) { + return false; + } + if (clientVersion && clientVersion < REQUIRE_VERSION) { + return false; + } + if (blocks && blocks < GetRequireHeight()) { + return false; + } - if (total <= 3 && success * 2 >= total) return true; + if (total <= 3 && success * 2 >= total) { + return true; + } - if (stat2H.reliability > 0.85 && stat2H.count > 2) return true; - if (stat8H.reliability > 0.70 && stat8H.count > 4) return true; - if (stat1D.reliability > 0.55 && stat1D.count > 8) return true; - if (stat1W.reliability > 0.45 && stat1W.count > 16) return true; - if (stat1M.reliability > 0.35 && stat1M.count > 32) return true; + if (stat2H.reliability > 0.85 && stat2H.count > 2) { + return true; + } + if (stat8H.reliability > 0.70 && stat8H.count > 4) { + return true; + } + if (stat1D.reliability > 0.55 && stat1D.count > 8) { + return true; + } + if (stat1W.reliability > 0.45 && stat1W.count > 16) { + return true; + } + if (stat1M.reliability > 0.35 && stat1M.count > 32) { + return true; + } return false; } int64_t GetBanTime() const { if (IsGood()) { return 0; } if (clientVersion && clientVersion < 31900) { return 604800; } if (stat1M.reliability - stat1M.weight + 1.0 < 0.15 && stat1M.count > 32) { return 30 * 86400; } if (stat1W.reliability - stat1W.weight + 1.0 < 0.10 && stat1W.count > 16) { return 7 * 86400; } if (stat1D.reliability - stat1D.weight + 1.0 < 0.05 && stat1D.count > 8) { return 1 * 86400; } return 0; } int64_t GetIgnoreTime() const { if (IsGood()) { return 0; } if (stat1M.reliability - stat1M.weight + 1.0 < 0.20 && stat1M.count > 2) { return 10 * 86400; } if (stat1W.reliability - stat1W.weight + 1.0 < 0.16 && stat1W.count > 2) { return 3 * 86400; } if (stat1D.reliability - stat1D.weight + 1.0 < 0.12 && stat1D.count > 2) { return 8 * 3600; } if (stat8H.reliability - stat8H.weight + 1.0 < 0.08 && stat8H.count > 2) { return 2 * 3600; } return 0; } void Update(bool good); friend class CAddrDb; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { uint8_t version = 4; READWRITE(version); READWRITE(ip); READWRITE(services); READWRITE(lastTry); uint8_t tried = ourLastTry != 0; READWRITE(tried); if (!tried) { return; } READWRITE(ourLastTry); READWRITE(ignoreTill); READWRITE(stat2H); READWRITE(stat8H); READWRITE(stat1D); READWRITE(stat1W); if (version >= 1) { READWRITE(stat1M); } else if (!ser_action.ForRead()) { *((CAddrStat *)(&stat1M)) = stat1W; } READWRITE(total); READWRITE(success); READWRITE(clientVersion); - if (version >= 2) READWRITE(clientSubVersion); - if (version >= 3) READWRITE(blocks); - if (version >= 4) READWRITE(ourLastSuccess); + if (version >= 2) { + READWRITE(clientSubVersion); + } + if (version >= 3) { + READWRITE(blocks); + } + if (version >= 4) { + READWRITE(ourLastSuccess); + } } }; class CAddrDbStats { public: int nBanned; int nAvail; int nTracked; int nNew; int nGood; int nAge; }; struct CServiceResult { CService service; bool fGood; int nBanTime; int nHeight; int nClientV; std::string strClientV; int64_t ourLastSuccess; }; /** * seen nodes * / \ * (a) banned nodes available nodes-------------- * / | \ * tracked nodes (b) unknown nodes (e) active nodes * / \ * (d) good nodes (c) non-good nodes */ class CAddrDb { private: mutable CCriticalSection cs; // number of address id's int nId; // map address id to address info (b,c,d,e) std::map idToInfo; // map ip to id (b,c,d,e) std::map ipToId; // sequence of tried nodes, in order we have tried connecting to them (c,d) std::deque ourId; // set of nodes not yet tried (b) std::set unkId; // set of good nodes (d, good e) std::set goodId; int nDirty; protected: // internal routines that assume proper locks are acquired // add an address void Add_(const CAddress &addr, bool force); // get an IP to test (must call Good_, Bad_, or Skipped_ on result // afterwards) bool Get_(CServiceResult &ip, int &wait); bool GetMany_(std::vector &ips, int max, int &wait); // mark an IP as good (must have been returned by Get_) void Good_(const CService &ip, int clientV, std::string clientSV, int blocks); // mark an IP as bad (and optionally ban it) (must have been returned by // Get_) void Bad_(const CService &ip, int ban); // mark an IP as skipped (must have been returned by Get_) void Skipped_(const CService &ip); // look up id of an IP int Lookup_(const CService &ip); // get a random set of IPs (shared lock only) void GetIPs_(std::set &ips, uint64_t requestedFlags, uint32_t max, const bool *nets); public: // nodes that are banned, with their unban time (a) std::map banned; void GetStats(CAddrDbStats &stats) const { LOCK(cs); stats.nBanned = banned.size(); stats.nAvail = idToInfo.size(); stats.nTracked = ourId.size(); stats.nGood = goodId.size(); stats.nNew = unkId.size(); if (ourId.size() > 0) { stats.nAge = time(nullptr) - idToInfo.at(ourId.at(0)).ourLastTry; } else { stats.nAge = 0; } } void ResetIgnores() { for (std::map::iterator it = idToInfo.begin(); it != idToInfo.end(); it++) { (*it).second.ignoreTill = 0; } } std::vector GetAll() { std::vector ret; LOCK(cs); for (std::deque::const_iterator it = ourId.begin(); it != ourId.end(); it++) { const CAddrInfo &info = idToInfo[*it]; if (info.success > 0) { ret.push_back(info.GetReport()); } } return ret; } // serialization code // format: // nVersion (0 for now) // n (number of ips in (b,c,d)) // CAddrInfo[n] // banned // acquires a shared lock (this does not suffice for read mode, but we // assume that only happens at startup, single-threaded) this way, dumping // does not interfere with GetIPs_, which is called from the DNS thread template void Serialize(Stream &s) const { LOCK(cs); int nVersion = 0; s << nVersion; CAddrDb *db = const_cast(this); int n = ourId.size() + unkId.size(); s << n; for (std::deque::const_iterator it = ourId.begin(); it != ourId.end(); it++) { std::map::iterator ci = db->idToInfo.find(*it); s << (*ci).second; } for (std::set::const_iterator it = unkId.begin(); it != unkId.end(); it++) { std::map::iterator ci = db->idToInfo.find(*it); s << (*ci).second; } s << banned; } template void Unserialize(Stream &s) { LOCK(cs); int nVersion; s >> nVersion; CAddrDb *db = const_cast(this); db->nId = 0; int n; s >> n; for (int i = 0; i < n; i++) { CAddrInfo info; s >> info; if (!info.GetBanTime()) { int id = db->nId++; db->idToInfo[id] = info; db->ipToId[info.ip] = id; if (info.ourLastTry) { db->ourId.push_back(id); - if (info.IsGood()) db->goodId.insert(id); + if (info.IsGood()) { + db->goodId.insert(id); + } } else { db->unkId.insert(id); } } } db->nDirty++; s >> banned; } void Add(const CAddress &addr, bool fForce = false) { LOCK(cs); Add_(addr, fForce); } void Add(const std::vector &vAddr, bool fForce = false) { LOCK(cs); for (size_t i = 0; i < vAddr.size(); i++) { Add_(vAddr[i], fForce); } } void Good(const CService &addr, int clientVersion, std::string clientSubVersion, int blocks) { LOCK(cs); Good_(addr, clientVersion, clientSubVersion, blocks); } void Skipped(const CService &addr) { LOCK(cs); Skipped_(addr); } void Bad(const CService &addr, int ban = 0) { LOCK(cs); Bad_(addr, ban); } bool Get(CServiceResult &ip, int &wait) { LOCK(cs); return Get_(ip, wait); } void GetMany(std::vector &ips, int max, int &wait) { LOCK(cs); while (max > 0) { CServiceResult ip = {}; if (!Get_(ip, wait)) { return; } ips.push_back(ip); max--; } } void ResultMany(const std::vector &ips) { LOCK(cs); for (size_t i = 0; i < ips.size(); i++) { if (ips[i].fGood) { Good_(ips[i].service, ips[i].nClientV, ips[i].strClientV, ips[i].nHeight); } else { Bad_(ips[i].service, ips[i].nBanTime); } } } void GetIPs(std::set &ips, uint64_t requestedFlags, uint32_t max, const bool *nets) { LOCK(cs); GetIPs_(ips, requestedFlags, max, nets); } }; #endif // BITCOIN_SEEDER_DB_H diff --git a/src/seeder/dns.cpp b/src/seeder/dns.cpp index 48554d3b1f..ac6e33873f 100644 --- a/src/seeder/dns.cpp +++ b/src/seeder/dns.cpp @@ -1,609 +1,662 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFLEN 512 #if defined IP_RECVDSTADDR #define DSTADDR_SOCKOPT IP_RECVDSTADDR #define DSTADDR_DATASIZE (CMSG_SPACE(sizeof(struct in6_addr))) #define dstaddr(x) (CMSG_DATA(x)) #elif defined IPV6_PKTINFO #define DSTADDR_SOCKOPT IPV6_PKTINFO #define DSTADDR_DATASIZE (CMSG_SPACE(sizeof(struct in6_pktinfo))) #define dstaddr(x) (&(((struct in6_pktinfo *)(CMSG_DATA(x)))->ipi6_addr)) #else #error "can't determine socket option" #endif union control_data { struct cmsghdr cmsg; uint8_t data[DSTADDR_DATASIZE]; }; -typedef enum { CLASS_IN = 1, QCLASS_ANY = 255 } dns_class; +typedef enum { + CLASS_IN = 1, + QCLASS_ANY = 255, +} dns_class; typedef enum { TYPE_A = 1, TYPE_NS = 2, TYPE_CNAME = 5, TYPE_SOA = 6, TYPE_MX = 15, TYPE_AAAA = 28, TYPE_SRV = 33, QTYPE_ANY = 255 } dns_type; int parse_name(const uint8_t **inpos, const uint8_t *inend, const uint8_t *inbuf, char *buf, size_t bufsize) { size_t bufused = 0; int init = 1; do { - if (*inpos == inend) return -1; + if (*inpos == inend) { + return -1; + } // read length of next component int octet = *((*inpos)++); if (octet == 0) { buf[bufused] = 0; return 0; } // add dot in output if (!init) { - if (bufused == bufsize - 1) return -2; + if (bufused == bufsize - 1) { + return -2; + } buf[bufused++] = '.'; } else init = 0; // handle references if ((octet & 0xC0) == 0xC0) { - if (*inpos == inend) return -1; + if (*inpos == inend) { + return -1; + } int ref = ((octet - 0xC0) << 8) + *((*inpos)++); - if (ref < 0 || ref >= (*inpos) - inbuf - 2) return -1; + if (ref < 0 || ref >= (*inpos) - inbuf - 2) { + return -1; + } const uint8_t *newbuf = inbuf + ref; return parse_name(&newbuf, (*inpos) - 2, inbuf, buf + bufused, bufsize - bufused); } - if (octet > 63) return -1; + if (octet > 63) { + return -1; + } // copy label while (octet) { - if (*inpos == inend) return -1; - if (bufused == bufsize - 1) return -2; + if (*inpos == inend) { + return -1; + } + if (bufused == bufsize - 1) { + return -2; + } int c = *((*inpos)++); - if (c == '.') return -1; + if (c == '.') { + return -1; + } octet--; buf[bufused++] = c; } } while (1); } // 0: k // -1: component > 63 characters // -2: insufficent space in output // -3: two subsequent dots static int write_name(uint8_t **outpos, const uint8_t *outend, const char *name, int offset) { while (*name != 0) { const char *dot = strchr(name, '.'); const char *fin = dot; - if (!dot) fin = name + strlen(name); - if (fin - name > 63) return -1; - if (fin == name) return -3; - if (outend - *outpos < fin - name + 2) return -2; + if (!dot) { + fin = name + strlen(name); + } + if (fin - name > 63) { + return -1; + } + if (fin == name) { + return -3; + } + if (outend - *outpos < fin - name + 2) { + return -2; + } *((*outpos)++) = fin - name; memcpy(*outpos, name, fin - name); *outpos += fin - name; - if (!dot) break; + if (!dot) { + break; + } name = dot + 1; } if (offset < 0) { // no reference - if (outend == *outpos) return -2; + if (outend == *outpos) { + return -2; + } *((*outpos)++) = 0; } else { - if (outend - *outpos < 2) return -2; + if (outend - *outpos < 2) { + return -2; + } *((*outpos)++) = (offset >> 8) | 0xC0; *((*outpos)++) = offset & 0xFF; } return 0; } static int write_record(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_type typ, dns_class cls, int ttl) { uint8_t *oldpos = *outpos; int error = 0; // name int ret = write_name(outpos, outend, name, offset); if (ret) { error = ret; goto error; } if (outend - *outpos < 8) { error = -4; goto error; } // type *((*outpos)++) = typ >> 8; *((*outpos)++) = typ & 0xFF; // class *((*outpos)++) = cls >> 8; *((*outpos)++) = cls & 0xFF; // ttl *((*outpos)++) = (ttl >> 24) & 0xFF; *((*outpos)++) = (ttl >> 16) & 0xFF; *((*outpos)++) = (ttl >> 8) & 0xFF; *((*outpos)++) = ttl & 0xFF; return 0; error: *outpos = oldpos; return error; } static int write_record_a(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const addr_t *ip) { - if (ip->v != 4) return -6; + if (ip->v != 4) { + return -6; + } uint8_t *oldpos = *outpos; int error = 0; int ret = write_record(outpos, outend, name, offset, TYPE_A, cls, ttl); - if (ret) return ret; + if (ret) { + return ret; + } if (outend - *outpos < 6) { error = -5; goto error; } // rdlength *((*outpos)++) = 0; *((*outpos)++) = 4; // rdata - for (int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { *((*outpos)++) = ip->data.v4[i]; + } return 0; error: *outpos = oldpos; return error; } static int write_record_aaaa(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const addr_t *ip) { - if (ip->v != 6) return -6; + if (ip->v != 6) { + return -6; + } uint8_t *oldpos = *outpos; int error = 0; int ret = write_record(outpos, outend, name, offset, TYPE_AAAA, cls, ttl); - if (ret) return ret; + if (ret) { + return ret; + } if (outend - *outpos < 6) { error = -5; goto error; } // rdlength *((*outpos)++) = 0; *((*outpos)++) = 16; // rdata - for (int i = 0; i < 16; i++) + for (int i = 0; i < 16; i++) { *((*outpos)++) = ip->data.v6[i]; + } return 0; error: *outpos = oldpos; return error; } static int write_record_ns(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const char *ns) { uint8_t *oldpos = *outpos; int ret = write_record(outpos, outend, name, offset, TYPE_NS, cls, ttl); if (ret) { return ret; } // Predeclare to avoid jumping over declaration. uint8_t *curpos; int error = 0; if (outend - *outpos < 2) { error = -5; goto error; } (*outpos) += 2; curpos = *outpos; ret = write_name(outpos, outend, ns, -1); if (ret) { error = ret; goto error; } curpos[-2] = (*outpos - curpos) >> 8; curpos[-1] = (*outpos - curpos) & 0xFF; return 0; error: *outpos = oldpos; return error; } static int write_record_soa(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const char *mname, const char *rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) { uint8_t *oldpos = *outpos; int ret = write_record(outpos, outend, name, offset, TYPE_SOA, cls, ttl); if (ret) { return ret; } // Predeclare variable to not jump over declarations. uint8_t *curpos; int error = 0; if (outend - *outpos < 2) { error = -5; goto error; } (*outpos) += 2; curpos = *outpos; ret = write_name(outpos, outend, mname, -1); if (ret) { error = ret; goto error; } ret = write_name(outpos, outend, rname, -1); if (ret) { error = ret; goto error; } if (outend - *outpos < 20) { error = -5; goto error; } *((*outpos)++) = (serial >> 24) & 0xFF; *((*outpos)++) = (serial >> 16) & 0xFF; *((*outpos)++) = (serial >> 8) & 0xFF; *((*outpos)++) = serial & 0xFF; *((*outpos)++) = (refresh >> 24) & 0xFF; *((*outpos)++) = (refresh >> 16) & 0xFF; *((*outpos)++) = (refresh >> 8) & 0xFF; *((*outpos)++) = refresh & 0xFF; *((*outpos)++) = (retry >> 24) & 0xFF; *((*outpos)++) = (retry >> 16) & 0xFF; *((*outpos)++) = (retry >> 8) & 0xFF; *((*outpos)++) = retry & 0xFF; *((*outpos)++) = (expire >> 24) & 0xFF; *((*outpos)++) = (expire >> 16) & 0xFF; *((*outpos)++) = (expire >> 8) & 0xFF; *((*outpos)++) = expire & 0xFF; *((*outpos)++) = (minimum >> 24) & 0xFF; *((*outpos)++) = (minimum >> 16) & 0xFF; *((*outpos)++) = (minimum >> 8) & 0xFF; *((*outpos)++) = minimum & 0xFF; curpos[-2] = (*outpos - curpos) >> 8; curpos[-1] = (*outpos - curpos) & 0xFF; return 0; error: *outpos = oldpos; return error; } static ssize_t dnshandle(dns_opt_t *opt, const uint8_t *inbuf, size_t insize, uint8_t *outbuf) { int error = 0; if (insize < 12) { // DNS header return -1; } // Predeclare various variables to avoid jumping over declarations. int have_ns = 0; int max_auth_size = 0; int nquestion = 0; // copy id outbuf[0] = inbuf[0]; outbuf[1] = inbuf[1]; // copy flags; outbuf[2] = inbuf[2]; outbuf[3] = inbuf[3]; // clear error outbuf[3] &= ~15; // check qr if (inbuf[2] & 128) { /* fprintf(stdout, "Got response?\n"); */ error = 1; goto error; } // check opcode if (((inbuf[2] & 120) >> 3) != 0) { /* fprintf(stdout, "Opcode nonzero?\n"); */ error = 4; goto error; } // unset TC outbuf[2] &= ~2; // unset RA outbuf[3] &= ~128; // check questions nquestion = (inbuf[4] << 8) + inbuf[5]; if (nquestion == 0) { /* fprintf(stdout, "No questions?\n"); */ error = 0; goto error; } if (nquestion > 1) { /* fprintf(stdout, "Multiple questions %i?\n", nquestion); */ error = 4; goto error; } { const uint8_t *inpos = inbuf + 12; const uint8_t *inend = inbuf + insize; char name[256]; int offset = inpos - inbuf; int ret = parse_name(&inpos, inend, inbuf, name, 256); if (ret == -1) { error = 1; goto error; } if (ret == -2) { error = 5; goto error; } int namel = strlen(name), hostl = strlen(opt->host); if (strcasecmp(name, opt->host) && (namel < hostl + 2 || name[namel - hostl - 1] != '.' || strcasecmp(name + namel - hostl, opt->host))) { error = 5; goto error; } if (inend - inpos < 4) { error = 1; goto error; } // copy question to output memcpy(outbuf + 12, inbuf + 12, inpos + 4 - (inbuf + 12)); // set counts outbuf[4] = 0; outbuf[5] = 1; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; outbuf[10] = 0; outbuf[11] = 0; // set qr outbuf[2] |= 128; int typ = (inpos[0] << 8) + inpos[1]; int cls = (inpos[2] << 8) + inpos[3]; inpos += 4; uint8_t *outpos = outbuf + (inpos - inbuf); uint8_t *outend = outbuf + BUFLEN; // fprintf(stdout, "DNS: Request host='%s' type=%i class=%i\n", name, // typ, cls); // calculate max size of authority section if (!((typ == TYPE_NS || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY))) { // authority section will be necessary, either NS or SOA uint8_t *newpos = outpos; write_record_ns(&newpos, outend, "", offset, CLASS_IN, 0, opt->ns); max_auth_size = newpos - outpos; newpos = outpos; write_record_soa(&newpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); - if (max_auth_size < newpos - outpos) + if (max_auth_size < newpos - outpos) { max_auth_size = newpos - outpos; + } // fprintf(stdout, "Authority section will claim %i bytes max\n", // max_auth_size); } // Answer section // NS records if ((typ == TYPE_NS || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY)) { int ret2 = write_record_ns(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->nsttl, opt->ns); // fprintf(stdout, "wrote NS record: %i\n", ret2); if (!ret2) { outbuf[7]++; have_ns++; } } // SOA records if ((typ == TYPE_SOA || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY) && opt->mbox) { int ret2 = write_record_soa(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); // fprintf(stdout, "wrote SOA record: %i\n", ret2); if (!ret2) { outbuf[7]++; } } // A/AAAA records if ((typ == TYPE_A || typ == TYPE_AAAA || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY)) { addr_t addr[32]; int naddr = opt->cb((void *)opt, name, addr, 32, typ == TYPE_A || typ == QTYPE_ANY, typ == TYPE_AAAA || typ == QTYPE_ANY); int n = 0; while (n < naddr) { int mustbreak = 1; if (addr[n].v == 4) { mustbreak = write_record_a(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->datattl, &addr[n]); } else if (addr[n].v == 6) { mustbreak = write_record_aaaa( &outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->datattl, &addr[n]); } // fprintf(stdout, "wrote A record: %i\n", mustbreak); if (mustbreak) { break; } n++; outbuf[7]++; } } // Authority section if (!have_ns && outbuf[7]) { int ret2 = write_record_ns(&outpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns); // fprintf(stdout, "wrote NS record: %i\n", ret2); if (!ret2) { outbuf[9]++; } } else if (!outbuf[7]) { // Didn't include any answers, so reply with SOA as this is a // negative response. If we replied with NS above we'd create a bad // horizontal referral loop, as the NS response indicates where the // resolver should try next. int ret2 = write_record_soa( &outpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); // fprintf(stdout, "wrote SOA record: %i\n", ret2); if (!ret2) { outbuf[9]++; } } // set AA outbuf[2] |= 4; return outpos - outbuf; } error: // set error outbuf[3] |= error & 0xF; // set counts outbuf[4] = 0; outbuf[5] = 0; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; outbuf[10] = 0; outbuf[11] = 0; return 12; } static int listenSocket = -1; int dnsserver(dns_opt_t *opt) { struct sockaddr_in6 si_other; int senderSocket = -1; senderSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); - if (senderSocket == -1) return -3; + if (senderSocket == -1) { + return -3; + } int replySocket; if (listenSocket == -1) { struct sockaddr_in6 si_me; if ((listenSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) { listenSocket = -1; return -1; } replySocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (replySocket == -1) { close(listenSocket); return -1; } int sockopt = 1; setsockopt(listenSocket, IPPROTO_IPV6, DSTADDR_SOCKOPT, &sockopt, sizeof sockopt); memset((char *)&si_me, 0, sizeof(si_me)); si_me.sin6_family = AF_INET6; si_me.sin6_port = htons(opt->port); si_me.sin6_addr = in6addr_any; - if (bind(listenSocket, (struct sockaddr *)&si_me, sizeof(si_me)) == -1) + if (bind(listenSocket, (struct sockaddr *)&si_me, sizeof(si_me)) == + -1) { return -2; + } } uint8_t inbuf[BUFLEN], outbuf[BUFLEN]; struct iovec iov[1] = { { .iov_base = inbuf, .iov_len = sizeof(inbuf), }, }; union control_data cmsg; msghdr msg; msg.msg_name = &si_other; msg.msg_namelen = sizeof(si_other); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = &cmsg; msg.msg_controllen = sizeof(cmsg); for (; 1; ++(opt->nRequests)) { ssize_t insize = recvmsg(listenSocket, &msg, 0); // uint8_t *addr = (uint8_t*)&si_other.sin_addr.s_addr; // fprintf(stdout, "DNS: Request %llu from %i.%i.%i.%i:%i of %i // bytes\n", (unsigned long long)(opt->nRequests), addr[0], addr[1], // addr[2], addr[3], ntohs(si_other.sin_port), (int)insize); - if (insize <= 0) continue; + if (insize <= 0) { + continue; + } ssize_t ret = dnshandle(opt, inbuf, insize, outbuf); - if (ret <= 0) continue; + if (ret <= 0) { + continue; + } bool handled = false; for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) { if (hdr->cmsg_level == IPPROTO_IP && hdr->cmsg_type == DSTADDR_SOCKOPT) { msg.msg_iov[0].iov_base = outbuf; msg.msg_iov[0].iov_len = ret; sendmsg(listenSocket, &msg, 0); msg.msg_iov[0].iov_base = inbuf; msg.msg_iov[0].iov_len = sizeof(inbuf); handled = true; } } - if (!handled) + if (!handled) { sendto(listenSocket, outbuf, ret, 0, (struct sockaddr *)&si_other, sizeof(si_other)); + } } return 0; }