2020-05-22 16:18:41 +03:00
|
|
|
/*
|
2023-01-20 15:34:40 -05:00
|
|
|
* Copyright (c) 2013-2023, The PurpleI2P Project
|
2020-05-22 16:18:41 +03:00
|
|
|
*
|
|
|
|
* This file is part of Purple i2pd project and licensed under BSD3
|
|
|
|
*
|
|
|
|
* See full license text in LICENSE file at top of project tree
|
|
|
|
*/
|
|
|
|
|
2016-02-11 00:00:00 +00:00
|
|
|
#include <sys/stat.h>
|
2023-02-11 16:22:02 -05:00
|
|
|
#include <unordered_map>
|
2023-02-14 09:50:32 -05:00
|
|
|
#include <list>
|
2023-02-14 09:33:10 -05:00
|
|
|
#include <thread>
|
2015-03-24 12:47:57 -04:00
|
|
|
#include <boost/property_tree/ptree.hpp>
|
2023-05-15 00:00:00 +00:00
|
|
|
#include <boost/property_tree/json_parser.hpp>
|
2015-11-03 09:15:49 -05:00
|
|
|
#include "Base.h"
|
2016-02-11 00:00:00 +00:00
|
|
|
#include "FS.h"
|
2015-11-03 09:15:49 -05:00
|
|
|
#include "Log.h"
|
2023-01-20 15:34:40 -05:00
|
|
|
#include "Timestamp.h"
|
2015-03-24 12:47:57 -04:00
|
|
|
#include "Profiling.h"
|
|
|
|
|
|
|
|
namespace i2p
|
|
|
|
{
|
|
|
|
namespace data
|
|
|
|
{
|
2023-02-11 16:22:02 -05:00
|
|
|
static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles;
|
2023-02-14 09:33:10 -05:00
|
|
|
static std::mutex g_ProfilesMutex;
|
2016-02-20 01:00:00 +00:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
static uint64_t GetTime ()
|
2023-02-11 16:22:02 -05:00
|
|
|
{
|
2023-05-15 00:00:00 +00:00
|
|
|
return i2p::util::GetSecondsSinceEpoch ();
|
2023-02-11 16:22:02 -05:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
|
2016-12-30 20:09:41 -05:00
|
|
|
RouterProfile::RouterProfile ():
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastUpdateTime (0), m_LastDeclineTime (0), m_LastUnreachableTime (0),
|
2015-06-05 15:55:21 -04:00
|
|
|
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
|
2023-05-05 16:14:54 -04:00
|
|
|
m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false)
|
2015-03-24 12:47:57 -04:00
|
|
|
{
|
|
|
|
}
|
2015-03-30 21:05:04 -04:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
std::string RouterProfile::Dump (const std::string& peerid)
|
2015-03-24 18:48:16 -04:00
|
|
|
{
|
|
|
|
boost::property_tree::ptree pt;
|
2023-05-15 00:00:00 +00:00
|
|
|
std::stringstream ss;
|
2015-06-05 15:55:21 -04:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
pt.put(PEER_PROFILE_PEER_ID, peerid);
|
|
|
|
/* "times" hash */
|
|
|
|
pt.put(PEER_PROFILE_LAST_UPDATE_TIME, m_LastUpdateTime);
|
|
|
|
pt.put(PEER_PROFILE_LAST_DECLINE_TIME, m_LastDeclineTime);
|
|
|
|
pt.put(PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime);
|
|
|
|
/* "tunnels" hash */
|
|
|
|
pt.put(PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed);
|
|
|
|
pt.put(PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined);
|
|
|
|
pt.put(PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied);
|
|
|
|
/* "usage" hash */
|
|
|
|
pt.put(PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken);
|
|
|
|
pt.put(PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected);
|
|
|
|
pt.put(PEER_PROFILE_USAGE_CONNECTED, m_HasConnected);
|
2016-02-11 00:00:00 +00:00
|
|
|
|
|
|
|
try {
|
2023-05-15 00:00:00 +00:00
|
|
|
/* convert ptree to single line json string */
|
|
|
|
boost::property_tree::write_json (ss, pt, false);
|
2016-02-11 00:00:00 +00:00
|
|
|
} catch (std::exception& ex) {
|
|
|
|
/* boost exception verbose enough */
|
2023-05-15 00:00:00 +00:00
|
|
|
LogPrint (eLogError, "Profiling: can't serialize data to json -- ", ex.what ());
|
2015-03-24 18:48:16 -04:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
return ss.str();
|
2016-02-11 00:00:00 +00:00
|
|
|
}
|
2015-03-25 08:45:50 -04:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
std::string RouterProfile::Load (const std::string& jsondata)
|
2015-03-25 08:45:50 -04:00
|
|
|
{
|
2016-02-11 00:00:00 +00:00
|
|
|
boost::property_tree::ptree pt;
|
2023-05-15 00:00:00 +00:00
|
|
|
std::stringstream ss(jsondata);
|
|
|
|
std::string peerid = "";
|
2016-02-11 00:00:00 +00:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
try {
|
|
|
|
boost::property_tree::read_json (ss, pt);
|
|
|
|
} catch (std::exception& ex) {
|
2016-02-11 00:00:00 +00:00
|
|
|
/* boost exception verbose enough */
|
2023-05-15 00:00:00 +00:00
|
|
|
LogPrint (eLogError, "Profiling: can't parse json data -- ", ex.what ());
|
|
|
|
return std::string("");
|
2016-02-11 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
try {
|
|
|
|
peerid = pt.get<std::string>(PEER_PROFILE_PEER_ID);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
LogPrint (eLogError, "Profiling: Can't read profile data: missing peerid");
|
|
|
|
return std::string("");
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
try {
|
|
|
|
/* "lasttime" hash */
|
|
|
|
m_LastUpdateTime = pt.get<int>(PEER_PROFILE_LAST_UPDATE_TIME, 0);
|
|
|
|
m_LastDeclineTime = pt.get<int>(PEER_PROFILE_LAST_DECLINE_TIME, 0);
|
|
|
|
m_LastUnreachableTime = pt.get<int>(PEER_PROFILE_LAST_UNREACHABLE_TIME, 0);
|
|
|
|
/* "tunnels" hash */
|
|
|
|
m_NumTunnelsAgreed = pt.get<int>(PEER_PROFILE_PARTICIPATION_AGREED, 0);
|
|
|
|
m_NumTunnelsDeclined = pt.get<int>(PEER_PROFILE_PARTICIPATION_DECLINED, 0);
|
|
|
|
m_NumTunnelsNonReplied = pt.get<int>(PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
|
|
|
|
/* "usage" hash */
|
|
|
|
m_NumTimesTaken = pt.get<int>(PEER_PROFILE_USAGE_TAKEN, 0);
|
|
|
|
m_NumTimesRejected = pt.get<int>(PEER_PROFILE_USAGE_REJECTED, 0);
|
|
|
|
m_HasConnected = pt.get<bool>(PEER_PROFILE_USAGE_CONNECTED, false);
|
|
|
|
} catch (boost::property_tree::ptree_bad_path& ex) {
|
|
|
|
LogPrint (eLogError, "Profiling: Can't read profile data: ", ex.what());
|
2016-02-11 00:00:00 +00:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
return peerid;
|
2016-02-11 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 18:48:16 -04:00
|
|
|
void RouterProfile::TunnelBuildResponse (uint8_t ret)
|
|
|
|
{
|
2023-05-15 00:00:00 +00:00
|
|
|
if (ret > 0) {
|
2015-03-24 18:48:16 -04:00
|
|
|
m_NumTunnelsDeclined++;
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastDeclineTime = GetTime ();
|
|
|
|
} else {
|
|
|
|
m_NumTunnelsAgreed++;
|
2023-01-20 15:34:40 -05:00
|
|
|
m_LastDeclineTime = 0;
|
2023-02-11 09:41:51 +03:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastUpdateTime = GetTime ();
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2015-03-28 16:09:34 -04:00
|
|
|
|
|
|
|
void RouterProfile::TunnelNonReplied ()
|
|
|
|
{
|
2023-05-15 00:00:00 +00:00
|
|
|
m_NumTunnelsNonReplied++;
|
2023-01-21 19:40:43 -05:00
|
|
|
if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3)
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastDeclineTime = GetTime ();
|
|
|
|
m_LastUpdateTime = GetTime ();
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2015-03-31 17:13:01 -04:00
|
|
|
|
2023-02-06 13:19:41 -05:00
|
|
|
void RouterProfile::Unreachable ()
|
|
|
|
{
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastUnreachableTime = GetTime ();
|
|
|
|
m_LastUpdateTime = GetTime ();
|
2023-02-11 09:41:51 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 16:14:54 -04:00
|
|
|
void RouterProfile::Connected ()
|
|
|
|
{
|
|
|
|
m_HasConnected = true;
|
2023-05-15 00:00:00 +00:00
|
|
|
m_LastUpdateTime = GetTime ();
|
|
|
|
}
|
|
|
|
|
2015-06-05 15:55:21 -04:00
|
|
|
bool RouterProfile::IsLowPartcipationRate () const
|
2015-03-31 17:13:01 -04:00
|
|
|
{
|
2015-06-05 15:55:21 -04:00
|
|
|
return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2015-04-13 18:41:19 -04:00
|
|
|
|
2015-06-05 15:55:21 -04:00
|
|
|
bool RouterProfile::IsLowReplyRate () const
|
2015-04-13 18:41:19 -04:00
|
|
|
{
|
|
|
|
auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined;
|
2015-06-05 15:55:21 -04:00
|
|
|
return m_NumTunnelsNonReplied > 10*(total + 1);
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
|
|
|
|
2023-01-20 15:34:40 -05:00
|
|
|
bool RouterProfile::IsDeclinedRecently ()
|
|
|
|
{
|
|
|
|
if (!m_LastDeclineTime) return false;
|
|
|
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
2023-02-09 18:49:35 -05:00
|
|
|
if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL ||
|
|
|
|
ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime)
|
2023-01-20 15:34:40 -05:00
|
|
|
m_LastDeclineTime = 0;
|
2023-02-06 13:19:41 -05:00
|
|
|
return (bool)m_LastDeclineTime;
|
2023-02-11 09:41:51 +03:00
|
|
|
}
|
|
|
|
|
2015-06-05 15:55:21 -04:00
|
|
|
bool RouterProfile::IsBad ()
|
2018-01-06 11:48:51 +08:00
|
|
|
{
|
2023-02-06 13:19:41 -05:00
|
|
|
if (IsDeclinedRecently () || IsUnreachable ()) return true;
|
2015-06-09 14:04:25 -04:00
|
|
|
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
|
2018-01-06 11:48:51 +08:00
|
|
|
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
|
2015-06-05 15:55:21 -04:00
|
|
|
{
|
2015-06-09 14:04:25 -04:00
|
|
|
// reset profile
|
|
|
|
m_NumTunnelsAgreed = 0;
|
|
|
|
m_NumTunnelsDeclined = 0;
|
|
|
|
m_NumTunnelsNonReplied = 0;
|
|
|
|
isBad = false;
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2015-06-05 15:55:21 -04:00
|
|
|
if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++;
|
2018-01-06 11:48:51 +08:00
|
|
|
return isBad;
|
2015-03-31 17:13:01 -04:00
|
|
|
}
|
2018-01-06 11:48:51 +08:00
|
|
|
|
2023-02-06 13:19:41 -05:00
|
|
|
bool RouterProfile::IsUnreachable ()
|
|
|
|
{
|
|
|
|
if (!m_LastUnreachableTime) return false;
|
|
|
|
auto ts = i2p::util::GetSecondsSinceEpoch ();
|
2023-02-09 18:49:35 -05:00
|
|
|
if (ts > m_LastUnreachableTime + PEER_PROFILE_UNREACHABLE_INTERVAL ||
|
|
|
|
ts + PEER_PROFILE_UNREACHABLE_INTERVAL < m_LastUnreachableTime)
|
2023-02-06 13:19:41 -05:00
|
|
|
m_LastUnreachableTime = 0;
|
|
|
|
return (bool)m_LastUnreachableTime;
|
2023-02-11 09:41:51 +03:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
|
2023-05-06 07:59:40 +00:00
|
|
|
bool RouterProfile::IsUseful() const {
|
|
|
|
return
|
|
|
|
m_NumTunnelsAgreed >= PEER_PROFILE_USEFUL_THRESHOLD ||
|
|
|
|
m_NumTunnelsDeclined >= PEER_PROFILE_USEFUL_THRESHOLD ||
|
|
|
|
m_NumTunnelsNonReplied >= PEER_PROFILE_USEFUL_THRESHOLD ||
|
|
|
|
m_HasConnected;
|
|
|
|
}
|
|
|
|
|
2023-02-11 09:41:51 +03:00
|
|
|
|
2015-03-24 18:48:16 -04:00
|
|
|
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash)
|
2015-03-24 12:47:57 -04:00
|
|
|
{
|
2023-02-14 09:33:10 -05:00
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> l(g_ProfilesMutex);
|
|
|
|
auto it = g_Profiles.find (identHash);
|
|
|
|
if (it != g_Profiles.end ())
|
|
|
|
return it->second;
|
2023-05-15 00:00:00 +00:00
|
|
|
}
|
|
|
|
LogPrint(eLogDebug, "Profiling: creating new profile for ", identHash.ToBase64());
|
2016-12-30 20:09:41 -05:00
|
|
|
auto profile = std::make_shared<RouterProfile> ();
|
2023-02-14 09:33:10 -05:00
|
|
|
std::unique_lock<std::mutex> l(g_ProfilesMutex);
|
2023-02-11 16:22:02 -05:00
|
|
|
g_Profiles.emplace (identHash, profile);
|
2015-03-25 08:45:50 -04:00
|
|
|
return profile;
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
2015-04-11 15:39:23 -04:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
void LoadProfilesDB () {
|
|
|
|
unsigned int loaded = 0, linenum = 0;
|
|
|
|
static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > new_db;
|
|
|
|
IdentHash identHash;
|
|
|
|
std::string oldDBDir = i2p::fs::DataDirPath("peerProfiles");
|
|
|
|
auto DBPath = i2p::fs::DataDirPath(PEER_PROFILES_DB_FILENAME);
|
|
|
|
if (i2p::fs::Exists(oldDBDir)) {
|
|
|
|
std::string oldDBBak = oldDBDir + ".bak";
|
|
|
|
LogPrint(eLogInfo, "Profiling: old peerProfiles/ directory still exists, you may safely remove it");
|
|
|
|
std::rename(oldDBDir.c_str(), oldDBBak.c_str());
|
|
|
|
}
|
|
|
|
if (!i2p::fs::Exists(DBPath))
|
|
|
|
return; /* no database yet */
|
2016-02-20 01:00:00 +00:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
std::ifstream in (DBPath);
|
|
|
|
if (!in.is_open()) {
|
|
|
|
LogPrint (eLogError, "Profiling: can't open profiles database ", DBPath);
|
|
|
|
return;
|
2023-02-14 09:50:32 -05:00
|
|
|
}
|
2023-02-11 16:22:02 -05:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
std::string line;
|
|
|
|
while (!(in.eof() || in.fail())) {
|
|
|
|
std::getline(in, line); linenum++;
|
|
|
|
if (line.empty()) continue;
|
|
|
|
if (line[0] != '{') {
|
|
|
|
LogPrint(eLogError, "Profiling: ignore profile data at line ", linenum);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto profile = std::make_shared<RouterProfile> ();
|
|
|
|
std::string peerid = profile->Load(line);
|
|
|
|
if (peerid.empty())
|
|
|
|
continue; /* load failed, errors logged */
|
|
|
|
identHash.FromBase64(peerid);
|
|
|
|
new_db.emplace(identHash, profile);
|
|
|
|
loaded++;
|
|
|
|
}
|
|
|
|
LogPrint (eLogInfo, "Profiling: loaded ", loaded, " profiles");
|
|
|
|
|
|
|
|
{ /* replace exiting database with just loaded */
|
2023-02-14 09:50:32 -05:00
|
|
|
std::unique_lock<std::mutex> l(g_ProfilesMutex);
|
|
|
|
g_Profiles.clear ();
|
2023-05-15 00:00:00 +00:00
|
|
|
g_Profiles = new_db;
|
2023-02-14 09:50:32 -05:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PruneExpiredProfiles () {
|
|
|
|
unsigned int pruned = 0;
|
2023-02-11 16:22:02 -05:00
|
|
|
auto ts = GetTime ();
|
2023-05-15 00:00:00 +00:00
|
|
|
std::unique_lock<std::mutex> l(g_ProfilesMutex);
|
|
|
|
for (auto it = g_Profiles.begin (); it != g_Profiles.end (); ) {
|
|
|
|
if ((ts - it->second->GetLastUpdateTime ()) >= PEER_PROFILE_EXPIRATION_TIMEOUT * 3600) {
|
|
|
|
it = g_Profiles.erase (it);
|
|
|
|
pruned++;
|
|
|
|
} else {
|
|
|
|
it++;
|
2023-02-14 09:33:10 -05:00
|
|
|
}
|
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
LogPrint(eLogInfo, "Profiling: pruned ", pruned, " expired peer profiles, ", g_Profiles.size(), " remains");
|
|
|
|
}
|
2016-02-11 00:00:00 +00:00
|
|
|
|
2023-05-15 00:00:00 +00:00
|
|
|
void SaveProfilesDB () {
|
|
|
|
unsigned int saved = 0;
|
|
|
|
auto DBPath = i2p::fs::DataDirPath(PEER_PROFILES_DB_FILENAME);
|
|
|
|
auto DBPathNew = DBPath + ".new";
|
|
|
|
std::ofstream out (DBPathNew);
|
|
|
|
std::unique_lock<std::mutex> l(g_ProfilesMutex);
|
|
|
|
if (!out.is_open()) {
|
|
|
|
LogPrint(eLogError, "Profiling: can't open database file ", DBPathNew);
|
|
|
|
return;
|
2015-04-11 15:39:23 -04:00
|
|
|
}
|
2023-05-15 00:00:00 +00:00
|
|
|
auto ts = GetTime ();
|
|
|
|
/* save "old enough" profiles */
|
|
|
|
for (auto& it : g_Profiles) {
|
|
|
|
if (it.second->IsUseful() && (ts - it.second->GetLastUpdateTime ()) < PEER_PROFILE_PERSIST_INTERVAL)
|
|
|
|
continue; /* too new */
|
|
|
|
out << it.second->Dump(it.first.ToBase64());
|
|
|
|
saved++;
|
|
|
|
}
|
|
|
|
out.flush();
|
|
|
|
out.close();
|
|
|
|
LogPrint(eLogDebug, "Profiling: db path is", DBPath);
|
|
|
|
std::rename(DBPathNew.c_str(), DBPath.c_str());
|
|
|
|
LogPrint(eLogInfo, "Profiling: saved ", saved, " peer profiles");
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearProfilesDB () {
|
|
|
|
g_Profiles.clear();
|
2016-02-11 00:00:00 +00:00
|
|
|
}
|
2018-01-06 11:48:51 +08:00
|
|
|
}
|
|
|
|
}
|