diff --git a/libi2pd_client/TorrentsDHT.cpp b/libi2pd_client/TorrentsDHT.cpp index 8c532899..c4e4df54 100644 --- a/libi2pd_client/TorrentsDHT.cpp +++ b/libi2pd_client/TorrentsDHT.cpp @@ -107,6 +107,18 @@ namespace torrents m_Buckets->nodes.clear (); } + size_t RoutingTable::GetNumBuckets () const + { + size_t num = 0; + auto bucket = m_Buckets; + while (bucket) + { + num++; + bucket = bucket->next; + } + return num; + } + Bucket * RoutingTable::FindBucket (const Torrent::InfoHash& id) const { if (!m_Buckets) return nullptr; @@ -189,6 +201,30 @@ namespace torrents return ret; } + std::optional RoutingTable::FindClosestNode (const Torrent::InfoHash& infoHash) const + { + auto nodes = FindClosestNodes (infoHash, 1); + if (nodes.empty ()) return {}; + return nodes.front ().first; + } + + std::list RoutingTable::GetExploratoryTargets () const + { + std::list ret; + auto bucket = m_Buckets; + while (bucket) + { + if (!bucket->IsFull ()) + { + auto middleID = bucket->GetMiddleID (); + if (middleID) + ret.emplace_back (*middleID); + } + bucket = bucket->next; + } + return ret; + } + std::string DHTTorrent::GetBEncodedPeers () const { std::vector peers; @@ -223,7 +259,8 @@ namespace torrents } TorrentsDHT::TorrentsDHT (TorrentsTunnel& tunnel, uint16_t port): - m_Tunnel (tunnel), m_Port (port) + m_Tunnel (tunnel), m_DHTUpdateCheckTimer (tunnel.GetService ()), m_Port (port), + m_NextDHTExploratoryTime (i2p::util::GetMonotonicSeconds () + DHT_INITIAL_EXPLORATORY_INTERVAL) { auto dest = tunnel.GetLocalDestination (); if (dest) @@ -250,10 +287,12 @@ namespace torrents filename = dest->GetIdentHash ().ToBase32 (); } Load (GetDHTFilePath (filename)); + ScheduleDHTUpdateCheck (); } void TorrentsDHT::Stop () { + m_DHTUpdateCheckTimer.cancel (); std::string filename ("nodest"); auto dest = m_Tunnel.GetLocalDestination (); if (dest) @@ -640,7 +679,17 @@ namespace torrents } } } + else if (query == "find_node") + { + auto node = std::make_shared(nodeInfo); + LogPrint (eLogDebug, "TorrentsDHT: find_node response received ", node->peer.ToBase64 ()); + if (m_Nodes.emplace (node->id, node).second && m_RoutingTable) + m_RoutingTable->AddNode (node->id); + } + else + LogPrint (eLogInfo, "TorrentsDHT: Response to unknown query ", query); } + m_Queries.erase (it); } else LogPrint (eLogInfo, "TorrentsDHT: Query now found"); @@ -756,6 +805,46 @@ namespace torrents }), toIdent, toPort, true); // raw } + + void TorrentsDHT::Explore () + { + if (m_RoutingTable) + { + auto targets = m_RoutingTable->GetExploratoryTargets (); + for (auto it: targets) + { + auto nodeID = m_RoutingTable->FindClosestNode (it); + if (nodeID) + { + auto it1 = m_Nodes.find (*nodeID); + if (it1 != m_Nodes.end ()) + SendFindNodeQuery (it, it1->second->peer, it1->second->port); + } + } + } + } + + void TorrentsDHT::ScheduleDHTUpdateCheck () + { + m_DHTUpdateCheckTimer.cancel (); + m_DHTUpdateCheckTimer.expires_after (std::chrono::seconds (DHT_UPDATE_CHECK_INTERVAL)); + m_DHTUpdateCheckTimer.async_wait (std::bind_front(&TorrentsDHT::HandleDHTUpdateCheckTimer, this)); + } + + void TorrentsDHT::HandleDHTUpdateCheckTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetMonotonicSeconds (); + if (ts > m_NextDHTExploratoryTime) + { + Explore (); + m_NextDHTExploratoryTime = ts + DHT_EXPLORATORY_INTERVAL + (m_Tunnel.GetLocalDestination () ? + m_Tunnel.GetLocalDestination ()->GetRng ()() % DHT_EXPLORATORY_INTERVAL_VARIANCE : 0); + } + ScheduleDHTUpdateCheck (); + } + } } } diff --git a/libi2pd_client/TorrentsDHT.h b/libi2pd_client/TorrentsDHT.h index 156d5220..e25961fe 100644 --- a/libi2pd_client/TorrentsDHT.h +++ b/libi2pd_client/TorrentsDHT.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "Identity.h" #include "I2PService.h" #include "util.h" @@ -34,6 +35,11 @@ namespace i2p { namespace torrents { + constexpr int DHT_UPDATE_CHECK_INTERVAL = 24; // in seconds + constexpr int DHT_EXPLORATORY_INTERVAL = 14*60; // in seconds + constexpr int DHT_EXPLORATORY_INTERVAL_VARIANCE = 120; // in seconds + constexpr int DHT_INITIAL_EXPLORATORY_INTERVAL = 90; // in seconds + using Distance = Torrent::InfoHash; struct NodeID: public Torrent::InfoHash { @@ -98,9 +104,12 @@ namespace torrents RoutingTable (const NodeID& ourNode); ~RoutingTable (); void CleanUp (); + size_t GetNumBuckets () const; bool AddNode (const NodeID& id); std::list > FindClosestNodes (const Torrent::InfoHash& infoHash, size_t num = 1) const; + std::optional FindClosestNode (const Torrent::InfoHash& infoHash) const; + std::list GetExploratoryTargets () const; private: @@ -179,16 +188,23 @@ namespace torrents std::filesystem::path GetDHTFilePath (std::string_view filename) const; void Save (const std::filesystem::path& file); void Load (const std::filesystem::path& file); + void Explore (); + + void ScheduleDHTUpdateCheck (); + void HandleDHTUpdateCheckTimer (const boost::system::error_code& ecode); private: TorrentsTunnel& m_Tunnel; + boost::asio::steady_timer m_DHTUpdateCheckTimer; uint16_t m_Port; NodeID m_NodeID; std::unique_ptr m_RoutingTable; std::map > m_Nodes; std::unordered_map > > m_Queries; std::map > m_Torrents; + + uint64_t m_NextDHTExploratoryTime; // monotonic seconds }; } }