1 /* 2 * Copyright (c) 2013-2020, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <sys/stat.h> 10 #include <boost/property_tree/ptree.hpp> 11 #include <boost/property_tree/ini_parser.hpp> 12 #include "Base.h" 13 #include "FS.h" 14 #include "Log.h" 15 #include "Profiling.h" 16 17 namespace i2p 18 { 19 namespace data 20 { 21 i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); 22 RouterProfile()23 RouterProfile::RouterProfile (): 24 m_LastUpdateTime (boost::posix_time::second_clock::local_time()), 25 m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), 26 m_NumTimesTaken (0), m_NumTimesRejected (0) 27 { 28 } 29 GetTime() const30 boost::posix_time::ptime RouterProfile::GetTime () const 31 { 32 return boost::posix_time::second_clock::local_time(); 33 } 34 UpdateTime()35 void RouterProfile::UpdateTime () 36 { 37 m_LastUpdateTime = GetTime (); 38 } 39 Save(const IdentHash & identHash)40 void RouterProfile::Save (const IdentHash& identHash) 41 { 42 // fill sections 43 boost::property_tree::ptree participation; 44 participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed); 45 participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined); 46 participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied); 47 boost::property_tree::ptree usage; 48 usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken); 49 usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected); 50 // fill property tree 51 boost::property_tree::ptree pt; 52 pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); 53 pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); 54 pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); 55 56 // save to file 57 std::string ident = identHash.ToBase64 (); 58 std::string path = m_ProfilesStorage.Path(ident); 59 60 try { 61 boost::property_tree::write_ini (path, pt); 62 } catch (std::exception& ex) { 63 /* boost exception verbose enough */ 64 LogPrint (eLogError, "Profiling: ", ex.what ()); 65 } 66 } 67 Load(const IdentHash & identHash)68 void RouterProfile::Load (const IdentHash& identHash) 69 { 70 std::string ident = identHash.ToBase64 (); 71 std::string path = m_ProfilesStorage.Path(ident); 72 boost::property_tree::ptree pt; 73 74 if (!i2p::fs::Exists(path)) 75 { 76 LogPrint(eLogWarning, "Profiling: No profile yet for ", ident); 77 return; 78 } 79 80 try 81 { 82 boost::property_tree::read_ini (path, pt); 83 } catch (std::exception& ex) 84 { 85 /* boost exception verbose enough */ 86 LogPrint (eLogError, "Profiling: ", ex.what ()); 87 return; 88 } 89 90 try 91 { 92 auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); 93 if (t.length () > 0) 94 m_LastUpdateTime = boost::posix_time::time_from_string (t); 95 if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) 96 { 97 try 98 { 99 // read participations 100 auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); 101 m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); 102 m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); 103 m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); 104 } 105 catch (boost::property_tree::ptree_bad_path& ex) 106 { 107 LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident); 108 } 109 try 110 { 111 // read usage 112 auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); 113 m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); 114 m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); 115 } 116 catch (boost::property_tree::ptree_bad_path& ex) 117 { 118 LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); 119 } 120 } 121 else 122 *this = RouterProfile (); 123 } 124 catch (std::exception& ex) 125 { 126 LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ()); 127 } 128 } 129 TunnelBuildResponse(uint8_t ret)130 void RouterProfile::TunnelBuildResponse (uint8_t ret) 131 { 132 UpdateTime (); 133 if (ret > 0) 134 m_NumTunnelsDeclined++; 135 else 136 m_NumTunnelsAgreed++; 137 } 138 TunnelNonReplied()139 void RouterProfile::TunnelNonReplied () 140 { 141 m_NumTunnelsNonReplied++; 142 UpdateTime (); 143 } 144 IsLowPartcipationRate() const145 bool RouterProfile::IsLowPartcipationRate () const 146 { 147 return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate 148 } 149 IsLowReplyRate() const150 bool RouterProfile::IsLowReplyRate () const 151 { 152 auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined; 153 return m_NumTunnelsNonReplied > 10*(total + 1); 154 } 155 IsBad()156 bool RouterProfile::IsBad () 157 { 158 auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; 159 if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) 160 { 161 // reset profile 162 m_NumTunnelsAgreed = 0; 163 m_NumTunnelsDeclined = 0; 164 m_NumTunnelsNonReplied = 0; 165 isBad = false; 166 } 167 if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++; 168 return isBad; 169 } 170 GetRouterProfile(const IdentHash & identHash)171 std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash) 172 { 173 auto profile = std::make_shared<RouterProfile> (); 174 profile->Load (identHash); // if possible 175 return profile; 176 } 177 InitProfilesStorage()178 void InitProfilesStorage () 179 { 180 m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); 181 m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); 182 } 183 DeleteObsoleteProfiles()184 void DeleteObsoleteProfiles () 185 { 186 struct stat st; 187 std::time_t now = std::time(nullptr); 188 189 std::vector<std::string> files; 190 m_ProfilesStorage.Traverse(files); 191 for (const auto& path: files) { 192 if (stat(path.c_str(), &st) != 0) { 193 LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); 194 continue; 195 } 196 if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) { 197 LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); 198 i2p::fs::Remove(path); 199 } 200 } 201 } 202 } 203 } 204