maintain connection to datagram tracker

This commit is contained in:
orignal
2026-08-30 19:01:22 -04:00
parent 9e342ffd72
commit d7080fecb9
5 changed files with 123 additions and 92 deletions
+21 -21
View File
@@ -490,8 +490,8 @@ namespace torrents
void Torrent::ParseTrackerResponse (size_t trackerID, std::string_view buf)
{
if (trackerID >= m_TrackersInfo.size ())
m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
if (trackerID >= m_TrackerStats.size ())
m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL,
0, 0, 0, i2p::util::GetSecondsSinceEpoch ()});
ParseDictionary (buf, [this, trackerID](std::string_view key, std::string_view buf)->size_t
{
@@ -501,8 +501,8 @@ namespace torrents
if (l)
{
int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, (int)value*1000); // in milliseconds
std::get<1>(m_TrackersInfo[trackerID]) = interval;
std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request
std::get<1>(m_TrackerStats[trackerID]) = interval;
std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request
}
return l;
}
@@ -511,13 +511,13 @@ namespace torrents
else if (key == "complete")
{
auto [seeders, l] = ExtractInteger (buf);
if (l) std::get<3>(m_TrackersInfo[trackerID]) = seeders;
if (l) std::get<3>(m_TrackerStats[trackerID]) = seeders;
return l;
}
else if (key == "incomplete")
{
auto [leechers, l] = ExtractInteger (buf);
if (l) std::get<4>(m_TrackersInfo[trackerID]) = leechers;
if (l) std::get<4>(m_TrackerStats[trackerID]) = leechers;
return l;
}
else if (key == "failure reason")
@@ -525,19 +525,19 @@ namespace torrents
auto [reason, l] = ExtractByteString (buf);
LogPrint (eLogError, "Torrents: Tracker error: ", reason);
// double interval if tracker failure
int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackersInfo[trackerID])*2);
std::get<1>(m_TrackersInfo[trackerID]) = interval;
std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval;
int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackerStats[trackerID])*2);
std::get<1>(m_TrackerStats[trackerID]) = interval;
std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval;
return l;
}
return 0;
});
std::get<5>(m_TrackersInfo[trackerID]) = i2p::util::GetSecondsSinceEpoch ();
std::get<5>(m_TrackerStats[trackerID]) = i2p::util::GetSecondsSinceEpoch ();
}
size_t Torrent::ParsePeers (size_t trackerID, std::string_view buf)
{
auto& peers = std::get<0>(m_TrackersInfo[trackerID]);
auto& peers = std::get<0>(m_TrackerStats[trackerID]);
peers.clear ();
auto [hashes, len] = ExtractByteString (buf);
while (!hashes.empty ())
@@ -551,10 +551,10 @@ namespace torrents
void Torrent::HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval,
const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers)
{
if (trackerID >= m_TrackersInfo.size ())
m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
if (trackerID >= m_TrackerStats.size ())
m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL,
0, 0, 0, i2p::util::GetSecondsSinceEpoch () });
auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackersInfo[trackerID];
auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackerStats[trackerID];
trackerRequestInterval = interval*1000; // milliseconds
nextRequestTime = i2p::util::GetMonotonicMilliseconds () + trackerRequestInterval;
seeders = numSeeders;
@@ -795,10 +795,10 @@ namespace torrents
std::unordered_set<i2p::data::IdentHash> Torrent::GetPeers () const
{
if (m_TrackersInfo.size () == 1)
return std::get<0>(m_TrackersInfo.front ());
if (m_TrackerStats.size () == 1)
return std::get<0>(m_TrackerStats.front ());
std::unordered_set<i2p::data::IdentHash> ret;
for (const auto& it: m_TrackersInfo)
for (const auto& it: m_TrackerStats)
{
const auto& peers = std::get<0>(it);
ret.insert (peers.begin (), peers.end ());
@@ -808,16 +808,16 @@ namespace torrents
uint64_t Torrent::GetNextTrackerRequestTime (size_t trackerID) const
{
if (trackerID < m_TrackersInfo.size ()) return std::get<2>(m_TrackersInfo[trackerID]);
if (trackerID < m_TrackerStats.size ()) return std::get<2>(m_TrackerStats[trackerID]);
return 0;
}
void Torrent::SetNextTrackerRequestTime (size_t trackerID, uint64_t ts)
{
if (trackerID >= m_TrackersInfo.size ())
m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
if (trackerID >= m_TrackerStats.size ())
m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL,
0, 0, 0, i2p::util::GetSecondsSinceEpoch ()});
std::get<2>(m_TrackersInfo[trackerID]) = ts;
std::get<2>(m_TrackerStats[trackerID]) = ts;
}
TorrentStatus Torrent::GetStatus () const
+9 -9
View File
@@ -146,12 +146,12 @@ namespace torrents
using RequestedBlock = std::tuple<uint32_t, uint32_t, uint32_t>; // (index, offset, len)
class Torrent final
{
using TrackerStats = std::tuple<std::unordered_set<i2p::data::IdentHash>, int, uint64_t, int, int, uint64_t>;
// (peers, interval, next tracker request time in monotonic milliseconds,
// seeders, leechers, last update time in seconds since epoch)
public:
using InfoHash = std::array<uint8_t, 20>;
using TrackerInfo = std::tuple<std::unordered_set<i2p::data::IdentHash>, int, uint64_t, int, int, uint64_t>;
// (peers, interval, next tracker request time in monotonic milliseconds,
// seeders, leechers, last update time in seconds since epoch)
Torrent (std::string_view buf);
void ParseTrackerResponse (size_t trackerID, std::string_view buf);
@@ -173,7 +173,7 @@ namespace torrents
std::list<std::pair<std::filesystem::path, size_t> >& GetFiles () { return m_Files; }
size_t GetLength () const { return m_Length; }
size_t GetPieceLength () const { return m_PieceLength; }
int GetInterval (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<1>(m_TrackersInfo[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; }
int GetInterval (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<1>(m_TrackerStats[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; }
const InfoHash& GetInfoHash () const { return m_InfoHash; }
size_t GetLeft () const;
std::string GetHexStringInfoHash () const; // in url format
@@ -210,10 +210,10 @@ namespace torrents
int GetNumUploadingToPeers () const { return m_NumUploadingToPeers; }
void SetNumUploadingToPeers (int numUploadingToPeers) { m_NumUploadingToPeers = numUploadingToPeers; }
int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<3>(m_TrackersInfo[trackerID]) : 0; }
int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<4>(m_TrackersInfo[trackerID]) : 0; }
int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<0>(m_TrackersInfo[trackerID]).size () : 0; }
uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<5>(m_TrackersInfo[trackerID]) : 0; }
int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<3>(m_TrackerStats[trackerID]) : 0; }
int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<4>(m_TrackerStats[trackerID]) : 0; }
int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<0>(m_TrackerStats[trackerID]).size () : 0; }
uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<5>(m_TrackerStats[trackerID]) : 0; }
private:
@@ -230,7 +230,7 @@ namespace torrents
size_t m_Length, m_PieceLength;
InfoHash m_InfoHash; // SHA1
std::vector<Piece> m_Pieces;
std::vector<TrackerInfo> m_TrackersInfo;
std::vector<TrackerStats> m_TrackerStats;
bool m_IsComplete, m_IsStopped;
std::list<std::pair<std::filesystem::path, size_t> > m_Files; // list of (path, length)
size_t m_Uploaded, m_Downloaded;
+5 -7
View File
@@ -411,12 +411,11 @@ namespace torrents
boost::json::array JSONRPCHandler::GetTrackers (std::shared_ptr<Torrent> torrent) const
{
boost::json::array trackers;
const auto& tunnelTrackers = m_Tunnel->GetTrackers ();
for (size_t i = 0; i < tunnelTrackers.size (); i++)
for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++)
{
boost::json::object tracker;
tracker["id"] = i;
tracker["announce"] = tunnelTrackers[i];
tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i);
tracker["tier"] = 0;
trackers.push_back (tracker);
}
@@ -426,14 +425,13 @@ namespace torrents
boost::json::array JSONRPCHandler::GetTrackerStats (std::shared_ptr<Torrent> torrent) const
{
boost::json::array trackers;
const auto& tunnelTrackers = m_Tunnel->GetTrackers ();
for (size_t i = 0; i < tunnelTrackers.size (); i++)
for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++)
{
boost::json::object tracker;
tracker["id"] = i;
tracker["announce"] = tunnelTrackers[i];
tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i);
i2p::http::URL announceURL;
announceURL.parse (tunnelTrackers[i]);
announceURL.parse (m_Tunnel->GetTrackerAnnounce (i));
tracker["host"]= announceURL.host;
tracker["announceState"] = 1;
tracker["scrapeState"] = 0;
+74 -44
View File
@@ -33,7 +33,12 @@ namespace torrents
m_PeerID += localDestination->GetIdentHash ().ToBase64 ();
m_PeerID.resize (20, '0');
if (!trackers.empty ())
boost::split(m_Trackers, trackers, boost::is_any_of(","), boost::token_compress_on);
{
std::vector<std::string> trackersList;
boost::split(trackersList, trackers, boost::is_any_of(","), boost::token_compress_on);
for (const auto& it: trackersList)
m_Trackers.emplace_back (TrackerInfo{ it, 0, 0, 0 });
}
}
void TorrentsTunnel::Start ()
@@ -473,7 +478,7 @@ namespace torrents
if (!torrent) return;
i2p::http::URL reqURL;
if (trackerID < m_Trackers.size())
reqURL.parse (m_Trackers[trackerID]);
reqURL.parse (std::get<0>(m_Trackers[trackerID]));
else
reqURL.parse (torrent->GetAnnounce ());
#if __cplusplus >= 202002L // C++20
@@ -487,7 +492,19 @@ namespace torrents
}
if (reqURL.schema == "udp")
{
ConnectToDatagramTracker (torrent, trackerID, reqURL.host, reqURL.port, event);
if (trackerID >= m_Trackers.size()) return;
auto ts = i2p::util::GetMonotonicMilliseconds ();
auto& [announce, connectionID, expiration, fromPort] = m_Trackers[trackerID];
if (connectionID && ts <= expiration)
SendAnnounceToDatagramTracker (trackerID, connectionID, torrent, reqURL.host, reqURL.port, fromPort, event);
else
{
if (!expiration || ts >= expiration) // don't connect if pending connection
{
expiration = ts + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT;
ConnectToDatagramTracker (trackerID, reqURL.host, reqURL.port);
}
}
return;
}
std::map<std::string, std::string> params;
@@ -604,7 +621,7 @@ namespace torrents
auto it = m_DatragramTrackerTransactions.begin ();
while (it != m_DatragramTrackerTransactions.end ())
{
if (ts > (std::get<5>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT)*1000LL)
if (ts > std::get<3>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT)
it = m_DatragramTrackerTransactions.erase (it);
else
it++;
@@ -820,8 +837,7 @@ namespace torrents
LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", std::string_view ((const char *)(buf + 4), len - 4));
}
void TorrentsTunnel::ConnectToDatagramTracker (std::shared_ptr<Torrent> torrent,
size_t trackerID, std::string_view dest, uint16_t port, TrackerAnnounceEvent event)
void TorrentsTunnel::ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port)
{
LogPrint (eLogDebug, "TorrentsTunnel: Connecting to datagram tracker ", dest, ":", port);
auto address = i2p::client::context.GetAddressBook ().GetAddress (dest);
@@ -840,8 +856,8 @@ namespace torrents
auto session = dgramDest->GetSession (address->identHash);
if (session)
{
m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (torrent,
trackerID, address->identHash, port, fromPort, i2p::util::GetMonotonicSeconds (), event));
m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (
trackerID, fromPort, std::shared_ptr<Torrent>(nullptr), i2p::util::GetMonotonicMilliseconds ()));
session->SetVersion (i2p::datagram::eDatagramV2); // send datagram2
dgramDest->SendDatagram (session, connectRequest, 16, fromPort, port);
}
@@ -871,48 +887,62 @@ namespace torrents
return;
}
uint64_t connectionID = bufbe64toh (buf + 4);
auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second;
if (!torrent.expired ())
SendAnnounceToDatagramTracker (transactionID, connectionID, torrent.lock (), ident, port, fromPort, event);
else
m_DatragramTrackerTransactions.erase (it);
auto [trackerID, fromPort, torrent, ts] = it->second;
if (trackerID < m_Trackers.size ())
{
auto& [announce, connID, connExpiration, connFromPort] = m_Trackers[trackerID];
connID = connectionID;
connExpiration = i2p::util::GetMonotonicMilliseconds () + DATAGRAM_TRACKER_CONNECTION_EXPIRATION;
connFromPort = fromPort;
}
m_DatragramTrackerTransactions.erase (it);
}
void TorrentsTunnel::SendAnnounceToDatagramTracker (uint32_t transactionID,
uint64_t connectionID, std::shared_ptr<Torrent> torrent, const i2p::data::IdentHash& ident,
uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event)
void TorrentsTunnel::SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID,
std::shared_ptr<Torrent> torrent, std::string_view dest, uint16_t port,
uint16_t fromPort, TrackerAnnounceEvent event)
{
auto localDestination = GetLocalDestination ();
auto dgramDest = localDestination->GetDatagramDestination ();
if (dgramDest)
LogPrint (eLogDebug, "TorrentsTunnel: Sending announce to datagram tracker ", dest, ":", port);
auto address = i2p::client::context.GetAddressBook ().GetAddress (dest);
if (address && address->IsIdentHash ())
{
uint8_t announce[98];
htobe64buf (announce, connectionID); // connection_id
htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action
htobe32buf (announce + 12, transactionID); // transaction_id
memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash
memcpy (announce + 36, m_PeerID.data (), 20); // peer_id
auto left = torrent->GetLeft ();
htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded
htobe64buf (announce + 64, left); // left
htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded
htobe32buf (announce + 80, event); // event
htobe32buf (announce + 84, 0); // IP address 0:not used
htobe32buf (announce + 88, 0); // key, ignored
int numWant = 0;
if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted))
numWant = TRACKER_MAX_NUM_WANT;
htobe32buf (announce + 92, numWant); // num_want
htobe16buf (announce + 96, fromPort); // from port
auto session = dgramDest->GetSession (ident);
if (session)
auto localDestination = GetLocalDestination ();
auto dgramDest = localDestination->GetDatagramDestination ();
if (dgramDest)
{
session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3
dgramDest->SendDatagram (session, announce, 98, fromPort, port);
uint8_t announce[98];
htobe64buf (announce, connectionID); // connection_id
htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action
uint32_t transactionID = localDestination->GetRng()();
htobe32buf (announce + 12, transactionID); // transaction_id
memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash
memcpy (announce + 36, m_PeerID.data (), 20); // peer_id
auto left = torrent->GetLeft ();
htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded
htobe64buf (announce + 64, left); // left
htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded
htobe32buf (announce + 80, event); // event
htobe32buf (announce + 84, 0); // IP address 0:not used
htobe32buf (announce + 88, 0); // key, ignored
int numWant = 0;
if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted))
numWant = TRACKER_MAX_NUM_WANT;
htobe32buf (announce + 92, numWant); // num_want
htobe16buf (announce + 96, fromPort); // from port
auto session = dgramDest->GetSession (address->identHash);
if (session)
{
m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (
trackerID, fromPort, torrent, i2p::util::GetMonotonicMilliseconds ()));
session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3
dgramDest->SendDatagram (session, announce, 98, fromPort, port);
}
else
LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session");
}
else
LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session");
}
else
LogPrint (eLogInfo, "TorrentsTunnel: Tracker not found: ", dest);
}
void TorrentsTunnel::HandleAnnounceResponse (const uint8_t * buf, size_t len)
@@ -930,7 +960,7 @@ namespace torrents
LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker transaction ", transactionID, " not found");
return;
}
auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second;
auto [trackerID, fromPort, torrent, ts] = it->second;
if (!torrent.expired ())
{
uint32_t interval = bufbe32toh (buf + 4);
+14 -11
View File
@@ -32,7 +32,8 @@ namespace i2p
namespace torrents
{
constexpr int TRACKER_RESPONSE_TIMEOUT = 8; // in seconds
constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 60; // in seconds
constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 10000; // in milliseconds
constexpr int DATAGRAM_TRACKER_CONNECTION_EXPIRATION = 60000; // in milliseconds
constexpr int TRACKER_REQUESTS_CHECK_TIMEOUT = 1900; // in milliseconds
constexpr int RECONNECT_CHECK_INTERVAL = 70; // in seconds
constexpr int TRACKER_REQUESTS_INTERVAL_VARIANCE = 3000; // in milliseconds
@@ -76,6 +77,9 @@ namespace torrents
void Stop () { StopWorkAndFinishTasks (); }
};
using TrackerInfo = std::tuple<std::string, uint64_t, uint64_t, uint16_t>;
// (announce, connection_id, connection expiration time in monotonic milliseconds, connction from_port)
public:
TorrentsTunnel (std::string_view name, std::shared_ptr<i2p::client::ClientDestination> localDestination,
@@ -86,7 +90,8 @@ namespace torrents
auto& GetDiskIOService () { return m_DiskIOService.GetService (); };
const std::string& GetPeerID () const { return m_PeerID; }
const std::vector<std::string>& GetTrackers () const { return m_Trackers; }
std::string_view GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : 0; }
size_t GetNumTrackers () const { return m_Trackers.size (); }
std::shared_ptr<Torrent> FindTorrent (const Torrent::InfoHash& infoHash) const;
std::shared_ptr<Torrent> FindTorrentByID (int id) const;
std::vector<int> GetTorrentIDs () const;
@@ -134,29 +139,27 @@ namespace torrents
void UpdateStats ();
void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void ConnectToDatagramTracker (std::shared_ptr<Torrent> torrent, size_t trackerID,
std::string_view dest, uint16_t port, TrackerAnnounceEvent event);
void ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port);
void HandleConnectResponse (const uint8_t * buf, size_t len);
void HandleErrorResponse (const uint8_t * buf, size_t len);
void HandleAnnounceResponse (const uint8_t * buf, size_t len);
void SendAnnounceToDatagramTracker (uint32_t transactionID, uint64_t connectionID,
std::shared_ptr<Torrent> torrent, const i2p::data::IdentHash& ident,
uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event);
void SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID,
std::shared_ptr<Torrent> torrent, std::string_view dest, uint16_t port,
uint16_t fromPort, TrackerAnnounceEvent event);
private:
std::string m_Name, m_PeerID; // 20 characters
std::filesystem::path m_TorrentsDir;
std::vector<std::string> m_Trackers;
std::vector<TrackerInfo> m_Trackers;
std::map<Torrent::InfoHash, std::shared_ptr<Torrent> > m_Torrents;
std::map<int, std::weak_ptr<Torrent> > m_TorrentsByID;
mutable std::mutex m_TorrentsMutex;
boost::asio::steady_timer m_TrackerRequestsCheckTimer, m_KeepAliveCheckTimer,
m_ReconnectCheckTimer, m_TorrentsStatusUpdateTimer;
DiskIOService m_DiskIOService;
std::unordered_map<uint32_t, std::tuple<std::weak_ptr<Torrent>, size_t, i2p::data::IdentHash,
uint16_t, uint16_t, uint64_t, TrackerAnnounceEvent> > m_DatragramTrackerTransactions;
// transactionID->(torrent, trackerID, ident, port, fromPort, ts in monotonic seconds, event)
std::unordered_map<uint32_t, std::tuple<size_t, uint16_t, std::weak_ptr<Torrent>, uint64_t > > m_DatragramTrackerTransactions;
// transactionID->(trackerID, from_port, torrent, timestamp monotonic milliseconds)
};
}