Compare commits

..

1 Commits

Author SHA1 Message Date
lcharles123
faa10eb88e Merge 6e639f0e6a into 5f1c599f81 2025-01-14 20:01:11 +03:00
23 changed files with 115 additions and 291 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -711,40 +711,18 @@ namespace crypto
return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false); return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false);
} }
static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{ {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
uint32_t iv[4]; uint32_t iv[4];
iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce
EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv); EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv);
int outlen = 0; int outlen = 0;
EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen); EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen);
EVP_EncryptFinal_ex(ctx, NULL, &outlen); EVP_EncryptFinal_ex(ctx, NULL, &outlen);
}
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
ChaCha20 (ctx, msg, msgLen, key, nonce, out);
EVP_CIPHER_CTX_free (ctx); EVP_CIPHER_CTX_free (ctx);
} }
ChaCha20Context::ChaCha20Context ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
ChaCha20Context::~ChaCha20Context ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out);
}
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info,
uint8_t * out, size_t outLen) uint8_t * out, size_t outLen)
{ {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -226,19 +226,6 @@ namespace crypto
// ChaCha20 // ChaCha20
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
class ChaCha20Context
{
public:
ChaCha20Context ();
~ChaCha20Context ();
void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
private:
EVP_CIPHER_CTX * m_Ctx;
};
// HKDF // HKDF
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32

View File

@@ -195,7 +195,7 @@ namespace client
m_IsPublic = itr->second != "true"; m_IsPublic = itr->second != "true";
} }
int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0; int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
std::map<std::string, int&> intOpts = { std::map<std::string, int&> intOpts = {
{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},

View File

@@ -27,15 +27,18 @@ namespace data
size_t Identity::FromBuffer (const uint8_t * buf, size_t len) size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{ {
if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow if ( len < DEFAULT_IDENTITY_SIZE ) {
memcpy (this, buf, DEFAULT_IDENTITY_SIZE); // buffer too small, don't overflow
return 0;
}
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE; return DEFAULT_IDENTITY_SIZE;
} }
IdentHash Identity::Hash () const IdentHash Identity::Hash () const
{ {
IdentHash hash; IdentHash hash;
SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash); SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
return hash; return hash;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -819,22 +819,17 @@ namespace transport
Terminate (); Terminate ();
return; return;
} }
std::shared_ptr<i2p::data::RouterProfile> profile; // not null if older
bool isOlder = false;
if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{ {
// received RouterInfo is older than one in netdb // received RouterInfo is older than one in netdb
isOlder = true; profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile
if (ri1->HasProfile ())
{
auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile
if (profile && profile->IsDuplicated ()) if (profile && profile->IsDuplicated ())
{ {
SendTerminationAndTerminate (eNTCP2Banned); SendTerminationAndTerminate (eNTCP2Banned);
return; return;
} }
} }
}
auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () : auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () :
(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ()); (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ());
@@ -849,12 +844,8 @@ namespace transport
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
{ {
if (isOlder) // older router? if (profile) // older router?
i2p::data::UpdateRouterProfile (ri1->GetIdentHash (), profile->Duplicated (); // mark router as duplicated in profile
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else else
LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
SendTerminationAndTerminate (eNTCP2Banned); SendTerminationAndTerminate (eNTCP2Banned);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -10,6 +10,7 @@
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <map> #include <map>
#include <random>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <stdexcept> #include <stdexcept>
@@ -39,7 +40,7 @@ namespace data
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr),
m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true),
m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) m_LastExploratorySelectionUpdateTime (0)
{ {
} }
@@ -118,9 +119,8 @@ namespace data
i2p::util::SetThreadName("NetDB"); i2p::util::SetThreadName("NetDB");
uint64_t lastManage = 0; uint64_t lastManage = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), lastObsoleteProfilesCleanup = lastProfilesCleanup;
lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup; int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0;
std::list<std::shared_ptr<const I2NPMessage> > msgs; std::list<std::shared_ptr<const I2NPMessage> > msgs;
while (m_IsRunning) while (m_IsRunning)
@@ -181,7 +181,7 @@ namespace data
LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk"); LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk");
} }
lastProfilesCleanup = mts; lastProfilesCleanup = mts;
profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE; profilesCleanupVariance = rand () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
} }
if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000) if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000)
@@ -197,20 +197,7 @@ namespace data
else else
LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk"); LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk");
lastObsoleteProfilesCleanup = mts; lastObsoleteProfilesCleanup = mts;
obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE; obsoleteProfilesCleanVariance = rand () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
}
if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance)
{
bool isApplying = m_ApplyingProfileUpdates.valid ();
if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
{
m_ApplyingProfileUpdates.get ();
isApplying = false;
}
if (!isApplying)
m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates ();
lastApplyingProfileUpdates = mts;
applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE;
} }
} }
catch (std::exception& ex) catch (std::exception& ex)
@@ -294,7 +281,6 @@ namespace data
} }
else else
{ {
r->CancelBufferToDelete (); // since an update received
if (CheckLogLevel (eLogDebug)) if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
updated = false; updated = false;
@@ -571,7 +557,7 @@ namespace data
while(n > 0) while(n > 0)
{ {
std::lock_guard<std::mutex> lock(m_RouterInfosMutex); std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
uint32_t idx = m_Rng () % m_RouterInfos.size (); uint32_t idx = rand () % m_RouterInfos.size ();
uint32_t i = 0; uint32_t i = 0;
for (const auto & it : m_RouterInfos) { for (const auto & it : m_RouterInfos) {
if(i >= idx) // are we at the random start point? if(i >= idx) // are we at the random start point?
@@ -674,20 +660,15 @@ namespace data
{ {
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
buffer = r->CopyBuffer (); buffer = r->CopyBuffer ();
}
if (!i2p::transport::transports.IsConnected (ident))
r->ScheduleBufferToDelete (); r->ScheduleBufferToDelete ();
}
if (buffer) if (buffer)
saveToDisk.emplace_back(ident.ToBase64 (), buffer); saveToDisk.push_back(std::make_pair(ident.ToBase64 (), buffer));
} }
r->SetUpdated (false); r->SetUpdated (false);
updatedCount++; updatedCount++;
continue; continue;
} }
else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL)
// since update was long time ago we assume that router is not connected anymore
r->ScheduleBufferToDelete ();
if (r->GetProfile ()->IsUnreachable ()) if (r->GetProfile ()->IsUnreachable ())
r->SetUnreachable (true); r->SetUnreachable (true);
// make router reachable back if too few routers or floodfills // make router reachable back if too few routers or floodfills
@@ -723,7 +704,7 @@ namespace data
{ {
if (r->IsFloodfill ()) deletedFloodfillsCount++; if (r->IsFloodfill ()) deletedFloodfillsCount++;
// delete RI file // delete RI file
removeFromDisk.emplace_back (ident.ToBase64()); removeFromDisk.push_back (ident.ToBase64());
deletedCount++; deletedCount++;
if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false;
} }
@@ -1350,7 +1331,7 @@ namespace data
if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE) if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{ {
std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection), std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection),
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng); NETDB_MAX_EXPLORATORY_SELECTION_SIZE, std::mt19937(ts));
} }
else else
std::swap (m_ExploratorySelection, eligible); std::swap (m_ExploratorySelection, eligible);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -16,7 +16,6 @@
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <future> #include <future>
#include <random>
#include "Base.h" #include "Base.h"
#include "Gzip.h" #include "Gzip.h"
@@ -186,11 +185,10 @@ namespace data
std::shared_ptr<NetDbRequests> m_Requests; std::shared_ptr<NetDbRequests> m_Requests;
bool m_PersistProfiles; bool m_PersistProfiles;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters; std::future<void> m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters;
std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection; std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
std::mt19937 m_Rng;
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool; i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool; i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -27,8 +27,6 @@ namespace data
static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles; static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles;
static std::mutex g_ProfilesMutex; static std::mutex g_ProfilesMutex;
static std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > g_PostponedUpdates;
static std::mutex g_PostponedUpdatesMutex;
RouterProfile::RouterProfile (): RouterProfile::RouterProfile ():
m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0), m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0),
@@ -261,14 +259,14 @@ namespace data
} }
auto profile = netdb.NewRouterProfile (); auto profile = netdb.NewRouterProfile ();
profile->Load (identHash); // if possible profile->Load (identHash); // if possible
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
g_Profiles.emplace (identHash, profile); g_Profiles.emplace (identHash, profile);
return profile; return profile;
} }
bool IsRouterBanned (const IdentHash& identHash) bool IsRouterBanned (const IdentHash& identHash)
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash); auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ()) if (it != g_Profiles.end ())
return it->second->IsUnreachable (); return it->second->IsUnreachable ();
@@ -292,7 +290,7 @@ namespace data
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp; std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp;
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{ {
if (ts - it->second->GetLastUpdateTime () > PEER_PROFILE_PERSIST_INTERVAL) if (ts - it->second->GetLastUpdateTime () > PEER_PROFILE_PERSIST_INTERVAL)
@@ -314,7 +312,7 @@ namespace data
{ {
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp; std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp;
{ {
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::swap (tmp, g_Profiles); std::swap (tmp, g_Profiles);
} }
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
@@ -349,7 +347,7 @@ namespace data
{ {
{ {
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
std::lock_guard<std::mutex> l(g_ProfilesMutex); std::unique_lock<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{ {
if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT) if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
@@ -361,47 +359,5 @@ namespace data
return std::async (std::launch::async, DeleteFilesFromDisk); return std::async (std::launch::async, DeleteFilesFromDisk);
} }
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update)
{
if (!update) return true;
std::shared_ptr<RouterProfile> profile;
{
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
profile = it->second;
}
if (profile)
{
update (profile);
return true;
}
// postpone
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.emplace_back (identHash, update);
return false;
}
static void ApplyPostponedUpdates (std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > >&& updates)
{
for (const auto& [ident, update] : updates)
{
auto profile = GetRouterProfile (ident);
update (profile);
}
}
std::future<void> FlushPostponedRouterProfileUpdates ()
{
if (g_PostponedUpdates.empty ()) return std::future<void>();
std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > updates;
{
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.swap (updates);
}
return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates));
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -11,7 +11,6 @@
#include <memory> #include <memory>
#include <future> #include <future>
#include <functional>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "Identity.h" #include "Identity.h"
@@ -45,8 +44,6 @@ namespace data
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
const int PEER_PROFILE_USEFUL_THRESHOLD = 3; const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds
class RouterProfile class RouterProfile
{ {
@@ -111,8 +108,6 @@ namespace data
std::future<void> DeleteObsoleteProfiles (); std::future<void> DeleteObsoleteProfiles ();
void SaveProfiles (); void SaveProfiles ();
std::future<void> PersistProfiles (); std::future<void> PersistProfiles ();
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update); // return true if updated immediately, and false if postponed
std::future<void> FlushPostponedRouterProfileUpdates ();
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -1136,12 +1136,12 @@ namespace data
void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len) void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len)
{ {
m_IsBufferScheduledToDelete = false;
if (!m_Buffer) if (!m_Buffer)
m_Buffer = NewBuffer (); m_Buffer = NewBuffer ();
if (len > m_Buffer->size ()) len = m_Buffer->size (); if (len > m_Buffer->size ()) len = m_Buffer->size ();
memcpy (m_Buffer->data (), buf, len); memcpy (m_Buffer->data (), buf, len);
m_Buffer->SetBufferLen (len); m_Buffer->SetBufferLen (len);
m_IsBufferScheduledToDelete = false;
} }
std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -294,7 +294,6 @@ namespace data
std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; }; std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; };
std::shared_ptr<Buffer> CopyBuffer () const; std::shared_ptr<Buffer> CopyBuffer () const;
void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; }; void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; };
void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; };
bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; }; bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; };
bool IsUpdated () const { return m_IsUpdated; }; bool IsUpdated () const { return m_IsUpdated; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -890,7 +890,7 @@ namespace transport
} }
auto session = std::make_shared<SSU2Session> (*this, router, address); auto session = std::make_shared<SSU2Session> (*this, router, address);
if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) if (!isValidEndpoint && router->GetProfile ()->HasLastEndpoint (address->IsV4 ()))
{ {
// router doesn't publish endpoint, but we connected before and hole punch might be alive // router doesn't publish endpoint, but we connected before and hole punch might be alive
auto ep = router->GetProfile ()->GetLastEndpoint (); auto ep = router->GetProfile ()->GetLastEndpoint ();
@@ -1528,11 +1528,6 @@ namespace transport
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len); return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
} }
void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
m_ChaCha20 (msg, msgLen, key, nonce, out);
}
void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to)
{ {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2025, The PurpleI2P Project * Copyright (c) 2022-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -86,7 +86,6 @@ namespace transport
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; }
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from); void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from);
@@ -207,7 +206,6 @@ namespace transport
mutable std::mutex m_ReceivedPacketsQueueMutex; mutable std::mutex m_ReceivedPacketsQueueMutex;
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
i2p::crypto::ChaCha20Context m_ChaCha20;
// proxy // proxy
bool m_IsThroughProxy; bool m_IsThroughProxy;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024-2025, The PurpleI2P Project * Copyright (c) 2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -46,7 +46,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
SetDestConnID (headerX[0]); SetDestConnID (headerX[0]);
// decrypt and handle payload // decrypt and handle payload
uint8_t * payload = buf + 32; uint8_t * payload = buf + 32;
@@ -183,7 +183,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12); memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send // send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ());
UpdateNumSentBytes (payloadSize + 32); UpdateNumSentBytes (payloadSize + 32);
@@ -305,7 +305,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12); memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send // send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
UpdateNumSentBytes (payloadSize + 32); UpdateNumSentBytes (payloadSize + 32);

View File

@@ -682,7 +682,7 @@ namespace transport
} }
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint64_t headerX[2]; uint64_t headerX[2];
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]); LogPrint (eLogWarning, "SSU2: Unexpected PeerTest message SourceConnID=", connID, " DestConnID=", headerX[0]);
break; break;
} }
@@ -748,7 +748,7 @@ namespace transport
payloadSize += 16; payloadSize += 16;
header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
m_Server.ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX);
m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated
m_SentHandshakePacket->payloadSize = payloadSize; m_SentHandshakePacket->payloadSize = payloadSize;
// send // send
@@ -775,7 +775,7 @@ namespace transport
} }
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint8_t headerX[48]; uint8_t headerX[48];
m_Server.ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX);
memcpy (&m_DestConnID, headerX, 8); memcpy (&m_DestConnID, headerX, 8);
uint64_t token; uint64_t token;
memcpy (&token, headerX + 8, 8); memcpy (&token, headerX + 8, 8);
@@ -874,7 +874,7 @@ namespace transport
m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created) m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted Noise payload from Session Created)
header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (kh2, payload + (payloadSize - 12));
m_Server.ChaCha20 (headerX, 48, kh2, nonce, headerX); i2p::crypto::ChaCha20 (headerX, 48, kh2, nonce, headerX);
m_State = eSSU2SessionStateSessionCreatedSent; m_State = eSSU2SessionStateSessionCreatedSent;
m_SentHandshakePacket->payloadSize = payloadSize; m_SentHandshakePacket->payloadSize = payloadSize;
// send // send
@@ -902,7 +902,7 @@ namespace transport
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
const uint8_t nonce[12] = {0}; const uint8_t nonce[12] = {0};
uint8_t headerX[48]; uint8_t headerX[48];
m_Server.ChaCha20 (buf + 16, 48, kh2, nonce, headerX); i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
// KDF for SessionCreated // KDF for SessionCreated
m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header)
m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk); m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk);
@@ -1178,19 +1178,14 @@ namespace transport
LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb");
return false; return false;
} }
std::shared_ptr<i2p::data::RouterProfile> profile; // not null if older
bool isOlder = false;
if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) if (ri->GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{ {
// received RouterInfo is older than one in netdb // received RouterInfo is older than one in netdb
isOlder = true; profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile
if (ri->HasProfile ())
{
auto profile = i2p::data::GetRouterProfile (ri->GetIdentHash ()); // retrieve profile
if (profile && profile->IsDuplicated ()) if (profile && profile->IsDuplicated ())
return false; return false;
} }
}
ri = ri1; ri = ri1;
m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address (); m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address ();
@@ -1203,28 +1198,15 @@ namespace transport
(!m_RemoteEndpoint.address ().is_v6 () || (!m_RemoteEndpoint.address ().is_v6 () ||
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), m_Address->host.to_v6 ().to_bytes ().data (), 8))) // temporary address
{ {
if (isOlder) // older router? if (profile) // older router?
i2p::data::UpdateRouterProfile (ri->GetIdentHash (), profile->Duplicated (); // mark router as duplicated in profile
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else else
LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host, LogPrint (eLogInfo, "SSU2: Host mismatch between published address ", m_Address->host,
" and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ()));
return false; return false;
} }
if (!m_Address->published) if (!m_Address->published)
{
if (ri->HasProfile ())
ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint);
else
i2p::data::UpdateRouterProfile (ri->GetIdentHash (),
[ep = m_RemoteEndpoint](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->SetLastEndpoint (ep);
});
}
SetRemoteIdentity (ri->GetRouterIdentity ()); SetRemoteIdentity (ri->GetRouterIdentity ());
AdjustMaxPayloadSize (); AdjustMaxPayloadSize ();
m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now
@@ -1282,7 +1264,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (m_Address->i, payload + (payloadSize - 12));
memset (nonce, 0, 12); memset (nonce, 0, 12);
m_Server.ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16);
// send // send
if (m_Server.AddPendingOutgoingSession (shared_from_this ())) if (m_Server.AddPendingOutgoingSession (shared_from_this ()))
m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
@@ -1304,7 +1286,7 @@ namespace transport
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint8_t h[32]; uint8_t h[32];
memcpy (h, header.buf, 16); memcpy (h, header.buf, 16);
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
memcpy (&m_DestConnID, h + 16, 8); memcpy (&m_DestConnID, h + 16, 8);
// decrypt // decrypt
CreateNonce (be32toh (header.h.packetNum), nonce); CreateNonce (be32toh (header.h.packetNum), nonce);
@@ -1356,7 +1338,7 @@ namespace transport
header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24)); header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12)); header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), payload + (payloadSize - 12));
memset (nonce, 0, 12); memset (nonce, 0, 12);
m_Server.ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16);
// send // send
m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint);
} }
@@ -1380,7 +1362,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
m_Server.ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX);
uint64_t token = headerX[1]; uint64_t token = headerX[1];
if (token) if (token)
m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); m_Server.UpdateOutgoingToken (m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT);
@@ -1429,7 +1411,7 @@ namespace transport
} }
uint8_t nonce[12] = {0}; uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token uint64_t headerX[2]; // sourceConnID, token
m_Server.ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
m_DestConnID = headerX[0]; m_DestConnID = headerX[0];
// decrypt and handle payload // decrypt and handle payload
uint8_t * payload = buf + 32; uint8_t * payload = buf + 32;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -101,13 +101,13 @@ namespace tunnel
TunnelMessageBlock block; TunnelMessageBlock block;
block.deliveryType = eDeliveryTypeLocal; block.deliveryType = eDeliveryTypeLocal;
block.data = msg; block.data = msg;
std::lock_guard<std::mutex> l(m_SendMutex); std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.PutTunnelDataMsg (block); m_Gateway.PutTunnelDataMsg (block);
} }
void TransitTunnelGateway::FlushTunnelDataMsgs () void TransitTunnelGateway::FlushTunnelDataMsgs ()
{ {
std::lock_guard<std::mutex> l(m_SendMutex); std::unique_lock<std::mutex> l(m_SendMutex);
m_Gateway.SendBuffer (); m_Gateway.SendBuffer ();
} }
@@ -130,22 +130,14 @@ namespace tunnel
EncryptTunnelMsg (tunnelMsg, newMsg); EncryptTunnelMsg (tunnelMsg, newMsg);
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
} }
void TransitTunnelEndpoint::FlushTunnelDataMsgs () void TransitTunnelEndpoint::FlushTunnelDataMsgs ()
{ {
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint.FlushI2NPMsgs (); m_Endpoint.FlushI2NPMsgs ();
} }
void TransitTunnelEndpoint::Cleanup ()
{
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint.Cleanup ();
}
std::string TransitTunnelEndpoint::GetNextPeerName () const std::string TransitTunnelEndpoint::GetNextPeerName () const
{ {
auto hash = m_Endpoint.GetCurrentHash (); auto hash = m_Endpoint.GetCurrentHash ();

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -100,7 +100,7 @@ namespace tunnel
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound m_Endpoint (false) {}; // transit endpoint is always outbound
void Cleanup () override; void Cleanup () override { m_Endpoint.Cleanup (); }
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override; void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
void FlushTunnelDataMsgs () override; void FlushTunnelDataMsgs () override;
@@ -109,7 +109,6 @@ namespace tunnel
private: private:
std::mutex m_HandleMutex;
TunnelEndpoint m_Endpoint; TunnelEndpoint m_Endpoint;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -160,8 +160,7 @@ namespace transport
m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0), m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0)
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
{ {
} }
@@ -339,7 +338,7 @@ namespace transport
if (m_IsNAT) if (m_IsNAT)
{ {
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
} }
} }
@@ -561,14 +560,7 @@ namespace transport
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer) bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer)
{ {
if (!peer->router) // reconnect if (!peer->router) // reconnect
{ peer->SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb
auto r = netdb.FindRouter (ident); // try to get new one from netdb
if (r)
{
peer->SetRouter (r);
r->CancelBufferToDelete ();
}
}
if (peer->router) // we have RI already if (peer->router) // we have RI already
{ {
if (peer->priority.empty ()) if (peer->priority.empty ())
@@ -655,7 +647,7 @@ namespace transport
return true; return true;
} }
void Transports::SetPriority (std::shared_ptr<Peer> peer) void Transports::SetPriority (std::shared_ptr<Peer> peer) const
{ {
static const std::vector<i2p::data::RouterInfo::SupportedTransports> static const std::vector<i2p::data::RouterInfo::SupportedTransports>
ntcp2Priority = ntcp2Priority =
@@ -681,7 +673,7 @@ namespace transport
peer->numAttempts = 0; peer->numAttempts = 0;
peer->priority.clear (); peer->priority.clear ();
bool isReal = peer->router->GetProfile ()->IsReal (); bool isReal = peer->router->GetProfile ()->IsReal ();
bool ssu2 = isReal ? (m_Rng () & 1) : false; // try NTCP2 if router is not confirmed real bool ssu2 = isReal ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real
const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
if (directTransports) if (directTransports)
{ {
@@ -709,8 +701,7 @@ namespace transport
// try recently connected SSU2 if any // try recently connected SSU2 if any
auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
peer->router->GetCompatibleTransports (false); peer->router->GetCompatibleTransports (false);
if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && if (supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))
peer->router->HasProfile ())
{ {
auto ep = peer->router->GetProfile ()->GetLastEndpoint (); auto ep = peer->router->GetProfile ()->GetLastEndpoint ();
if (!ep.address ().is_unspecified () && ep.port ()) if (!ep.address ().is_unspecified () && ep.port ())
@@ -800,7 +791,7 @@ namespace transport
} }
else else
{ {
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service) if (m_Service)
{ {
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service); auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -838,7 +829,7 @@ namespace transport
} }
else else
{ {
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service) if (m_Service)
{ {
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service); auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -895,11 +886,7 @@ namespace transport
auto transport = peer->priority[peer->numAttempts-1]; auto transport = peer->priority[peer->numAttempts-1];
if (transport == i2p::data::RouterInfo::eNTCP2V4 || if (transport == i2p::data::RouterInfo::eNTCP2V4 ||
transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh) transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh)
i2p::data::UpdateRouterProfile (ident, peer->router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Connected (); // outgoing NTCP2 connection if always real
});
i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable
} }
peer->numAttempts = 0; peer->numAttempts = 0;
@@ -934,11 +921,7 @@ namespace transport
session->SendI2NPMessages (msgs); // send DatabaseStore session->SendI2NPMessages (msgs); // send DatabaseStore
} }
auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed
i2p::data::UpdateRouterProfile (ident, if (r) r->GetProfile ()->Connected ();
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Connected ();
});
auto ts = i2p::util::GetSecondsSinceEpoch (); auto ts = i2p::util::GetSecondsSinceEpoch ();
auto peer = std::make_shared<Peer>(r, ts); auto peer = std::make_shared<Peer>(r, ts);
peer->sessions.push_back (session); peer->sessions.push_back (session);
@@ -971,15 +954,10 @@ namespace transport
ConnectToPeer (ident, peer); ConnectToPeer (ident, peer);
} }
else else
{
{ {
std::lock_guard<std::mutex> l(m_PeersMutex); std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (it); m_Peers.erase (it);
} }
// delete buffer of just disconnected router
auto r = i2p::data::netdb.FindRouter (ident);
if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete ();
}
} }
} }
}); });
@@ -1028,7 +1006,7 @@ namespace transport
if (session) if (session)
session->SendLocalRouterInfo (true); session->SendLocalRouterInfo (true);
it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
} }
++it; ++it;
} }
@@ -1052,7 +1030,7 @@ namespace transport
if (ecode != boost::asio::error::operation_aborted) if (ecode != boost::asio::error::operation_aborted)
{ {
PeerTest (); PeerTest ();
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
} }
} }
@@ -1199,7 +1177,7 @@ namespace transport
} }
/** XXX: if routes are not restricted this dies */ /** XXX: if routes are not restricted this dies */
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const
{ {
{ {
std::lock_guard<std::mutex> l(m_FamilyMutex); std::lock_guard<std::mutex> l(m_FamilyMutex);
@@ -1208,7 +1186,7 @@ namespace transport
if(sz > 1) if(sz > 1)
{ {
auto it = m_TrustedFamilies.begin (); auto it = m_TrustedFamilies.begin ();
std::advance(it, m_Rng() % sz); std::advance(it, rand() % sz);
fam = *it; fam = *it;
} }
else if (sz == 1) else if (sz == 1)
@@ -1226,7 +1204,7 @@ namespace transport
if(sz == 1) if(sz == 1)
return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
auto it = m_TrustedRouters.begin(); auto it = m_TrustedRouters.begin();
std::advance(it, m_Rng() % sz); std::advance(it, rand() % sz);
return i2p::data::netdb.FindRouter(*it); return i2p::data::netdb.FindRouter(*it);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -20,7 +20,6 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <random>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "TransportSession.h" #include "TransportSession.h"
#include "SSU2.h" #include "SSU2.h"
@@ -107,8 +106,7 @@ namespace transport
}; };
const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds
const int PEER_TEST_INTERVAL = 68*60; // in seconds const int PEER_TEST_INTERVAL = 71; // in minutes
const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds
const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds
const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds
const int MAX_NUM_DELAYED_MESSAGES = 150; const int MAX_NUM_DELAYED_MESSAGES = 150;
@@ -170,7 +168,7 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const; std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const;
/** get a trusted first hop for restricted routes */ /** get a trusted first hop for restricted routes */
std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer(); std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
/** do we want to use restricted routes? */ /** do we want to use restricted routes? */
bool RoutesRestricted() const; bool RoutesRestricted() const;
/** restrict routes to use only these router families for first hops */ /** restrict routes to use only these router families for first hops */
@@ -193,7 +191,7 @@ namespace transport
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident); void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs); std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs);
bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer); bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer);
void SetPriority (std::shared_ptr<Peer> peer); void SetPriority (std::shared_ptr<Peer> peer) const;
void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void HandlePeerTestTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode);
void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode);
@@ -241,7 +239,6 @@ namespace transport
mutable std::mutex m_TrustedRoutersMutex; mutable std::mutex m_TrustedRoutersMutex;
i2p::I2NPMessagesHandler m_LoopbackHandler; i2p::I2NPMessagesHandler m_LoopbackHandler;
std::mt19937 m_Rng;
public: public:

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -179,12 +179,9 @@ namespace tunnel
{ {
uint8_t ret = hop->GetRetCode (msg + 1); uint8_t ret = hop->GetRetCode (msg + 1);
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
if (hop->ident) auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), if (profile)
[ret](std::shared_ptr<i2p::data::RouterProfile> profile) profile->TunnelBuildResponse (ret);
{
if (profile) profile->TunnelBuildResponse (ret);
});
if (ret) if (ret)
// if any of participants declined the tunnel is not established // if any of participants declined the tunnel is not established
established = false; established = false;
@@ -746,11 +743,11 @@ namespace tunnel
while (hop) while (hop)
{ {
if (hop->ident) if (hop->ident)
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (),
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{ {
if (profile) profile->TunnelNonReplied (); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
}); if (profile)
profile->TunnelNonReplied ();
}
hop = hop->next; hop = hop->next;
} }
} }

View File

@@ -33,7 +33,7 @@ namespace tunnel
struct Fragment struct Fragment
{ {
Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size): Fragment (bool last, uint64_t t, const uint8_t * buf, size_t size):
isLastFragment (last), receiveTime (t), data (size) { memcpy (data.data(), buf, size); }; isLastFragment (last), receiveTime (t), data (buf, buf + size) {};
bool isLastFragment; bool isLastFragment;
uint64_t receiveTime; // milliseconds since epoch uint64_t receiveTime; // milliseconds since epoch
std::vector<uint8_t> data; std::vector<uint8_t> data;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -560,7 +560,7 @@ namespace tunnel
i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false);
if (hop) if (hop)
{ {
if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ()) if (!hop->GetProfile ()->IsBad ())
break; break;
} }
else if (tryClient) else if (tryClient)
@@ -588,7 +588,7 @@ namespace tunnel
(inbound && i2p::transport::transports.GetNumPeers () > 25)) (inbound && i2p::transport::transports.GetNumPeers () > 25))
{ {
auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ());
if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) && if (r && r->IsECIES () && !r->GetProfile ()->IsBad () &&
(numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4
{ {
prevHop = r; prevHop = r;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013-2025, The PurpleI2P Project * Copyright (c) 2013-2024, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
@@ -1350,14 +1350,12 @@ namespace client
LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ());
} }
decltype(m_Sessions) sessions;
{ {
std::unique_lock<std::mutex> l(m_SessionsMutex); std::unique_lock<std::mutex> l(m_SessionsMutex);
m_Sessions.swap (sessions); for (auto& it: m_Sessions)
}
for (auto& it: sessions)
it.second->Close (); it.second->Close ();
m_Sessions.clear ();
}
StopIOService (); StopIOService ();
} }