Files
i2pd/libi2pd/Destination.cpp
T
acetone acc0f94e49 refresh offline-signed transient on destination re-create
A repeated SESSION CREATE (SAM/I2CP) presenting a fresh offline-signature
transient of the same master resolves to the same identity hash, so
CreateNewLocalDestination returns the existing destination and the new
transient is dropped: the destination keeps signing LeaseSets with the old
transient and goes offline at its expiry.

Adopt the newer transient in place (swap keys and republish the LeaseSet,
without touching tunnels or streams) when its expiry is later, so an offline
transient can be rotated before it expires without rebuilding the destination.
2026-06-19 21:43:24 +03:00

1663 lines
57 KiB
C++

/*
* Copyright (c) 2013-2026, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <algorithm>
#include <cassert>
#include <string>
#include <set>
#include <vector>
#include <charconv>
#include <boost/algorithm/string.hpp>
#include "Crypto.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Log.h"
#include "FS.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "Destination.h"
namespace i2p
{
namespace client
{
LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service,
bool isPublic, const i2p::util::Mapping * params):
m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0),
m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service),
m_PublishVerificationTimer (m_Service), m_PublishDelayTimer (m_Service), m_CleanupTimer (m_Service),
m_LeaseSetType (DEFAULT_LEASESET_TYPE), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE)
{
int inLen = DEFAULT_INBOUND_TUNNEL_LENGTH;
int inQty = DEFAULT_INBOUND_TUNNELS_QUANTITY;
int outLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH;
int outQty = DEFAULT_OUTBOUND_TUNNELS_QUANTITY;
int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE;
int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE;
int numTags = DEFAULT_TAGS_TO_SEND;
bool isHighBandwidth = true;
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
std::string_view explicitPeersStr, trustedRoutersStr, inboundRandomKeyStr, outboundRandomKeyStr;
try
{
if (params)
{
params->Get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen);
params->Get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen);
params->Get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQty);
params->Get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQty);
params->Get (I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE, inVar);
params->Get (I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE, outVar);
params->Get (I2CP_PARAM_TAGS_TO_SEND, numTags);
LogPrint (eLogInfo, "Destination: Parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags");
int ratchetsInboundTags = 0;
if (params->Get (I2CP_PARAM_RATCHET_INBOUND_TAGS, ratchetsInboundTags))
{
if (ratchetsInboundTags && ratchetsInboundTags < i2p::garlic::ECIESX25519_MIN_NUM_GENERATED_TAGS)
ratchetsInboundTags = i2p::garlic::ECIESX25519_MIN_NUM_GENERATED_TAGS;
SetNumRatchetInboundTags (ratchetsInboundTags);
}
explicitPeersStr = (*params)[I2CP_PARAM_EXPLICIT_PEERS];
trustedRoutersStr = (*params)[I2CP_PARAM_TRUSTED_ROUTERS];
inboundRandomKeyStr = (*params)[I2CP_PARAM_INBOUND_RANDOM_KEY];
outboundRandomKeyStr = (*params)[I2CP_PARAM_OUTBOUND_RANDOM_KEY];
m_Nickname = (*params)[I2CP_PARAM_INBOUND_NICKNAME];
if (m_Nickname.empty ()) // try outbound
m_Nickname = (*params)[I2CP_PARAM_OUTBOUND_NICKNAME];
// otherwise we set default nickname in Start when we know local address
bool dontPublishLeaseSet = true;
if (params->Get (I2CP_PARAM_DONT_PUBLISH_LEASESET, dontPublishLeaseSet))
m_IsPublic = !dontPublishLeaseSet; // override isPublic
params->Get (I2CP_PARAM_LEASESET_TYPE, m_LeaseSetType);
if (m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
// authentication for encrypted LeaseSet
int authType = 0;
if (params->Get (I2CP_PARAM_LEASESET_AUTH_TYPE, authType))
{
if (authType >= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE && authType <= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK)
m_AuthType = authType;
else
LogPrint (eLogError, "Destination: Unknown auth type: ", authType);
}
}
auto leaseSetPrivKey = (*params)[I2CP_PARAM_LEASESET_PRIV_KEY];
if (!leaseSetPrivKey.empty ())
{
m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>());
if (m_LeaseSetPrivKey->FromBase64 (leaseSetPrivKey) != 32)
{
LogPrint(eLogCritical, "Destination: Invalid value i2cp.leaseSetPrivKey: ", leaseSetPrivKey);
m_LeaseSetPrivKey.reset (nullptr);
}
}
int streamingProfile = 0;
if (params->Get (I2CP_PARAM_STREAMING_PROFILE, streamingProfile))
isHighBandwidth = streamingProfile != STREAMING_PROFILE_INTERACTIVE;
}
}
catch (std::exception & ex)
{
LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what());
}
SetNumTags (numTags);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth);
if (!explicitPeersStr.empty ())
m_Pool->SetExplicitPeers (i2p::data::ExtractIdentHashes (explicitPeersStr));
if (!trustedRoutersStr.empty ())
m_Pool->SetTrustedRouters (i2p::data::ExtractIdentHashes (trustedRoutersStr));
if (!inboundRandomKeyStr.empty ())
{
uint8_t key[32]; // might be 32 bytes, but only first 16 bytes are used
if (i2p::data::Base64ToByteStream (inboundRandomKeyStr, key, 32) >= 16)
m_Pool->SetInboundPeerOrderingKey (key);
}
if (!outboundRandomKeyStr.empty ())
{
uint8_t key[32]; // might be 32 bytes, but only first 16 bytes are used
if (i2p::data::Base64ToByteStream (outboundRandomKeyStr, key, 32) >= 16)
m_Pool->SetOutboundPeerOrderingKey (key);
}
if(params)
{
int maxLatency = 0;
if (params->Get (I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency))
{
int minLatency = 0;
if (params->Get (I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency))
{
if (minLatency > 0 && maxLatency > 0)
{
// set tunnel pool latency
LogPrint(eLogInfo, "Destination: Requiring tunnel latency [", minLatency, "ms, ", maxLatency, "ms]");
m_Pool->RequireLatency(minLatency, maxLatency);
}
}
}
}
}
LeaseSetDestination::~LeaseSetDestination ()
{
if (m_Pool)
i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool);
for (auto& it: m_LeaseSetRequests)
it.second->Complete (nullptr);
}
void LeaseSetDestination::Start ()
{
if (m_Nickname.empty ())
m_Nickname = i2p::data::GetIdentHashAbbreviation (GetIdentHash ()); // set default nickname
LoadTags ();
m_Pool->SetLocalDestination (shared_from_this ());
m_Pool->SetActive (true);
m_CleanupTimer.expires_after (std::chrono::seconds (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
void LeaseSetDestination::Stop ()
{
m_CleanupTimer.cancel ();
m_PublishConfirmationTimer.cancel ();
m_PublishVerificationTimer.cancel ();
if (m_Pool)
{
m_Pool->SetLocalDestination (nullptr);
i2p::tunnel::tunnels.StopTunnelPool (m_Pool);
}
SaveTags ();
CleanUp (); // GarlicDestination
}
bool LeaseSetDestination::Reconfigure (const i2p::util::Mapping& params)
{
bool dontPublishLeaseSet = !m_IsPublic;
params.Get(I2CP_PARAM_DONT_PUBLISH_LEASESET, dontPublishLeaseSet);
m_IsPublic = !dontPublishLeaseSet;
auto numTags = GetNumTags ();
params.Get (I2CP_PARAM_TAGS_TO_SEND, numTags);
auto numRatchetInboundTags = GetNumRatchetInboundTags ();
params.Get (I2CP_PARAM_RATCHET_INBOUND_TAGS, numRatchetInboundTags);
auto pool = GetTunnelPool();
auto inLen = pool->GetNumInboundHops();
params.Get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen);
auto outLen = pool->GetNumOutboundHops();
params.Get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen);
auto inQuant = pool->GetNumInboundTunnels();
params.Get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQuant);
auto outQuant = pool->GetNumOutboundTunnels();
params.Get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQuant);
int minLatency = 0;
params.Get (I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency);
int maxLatency = 0;
params.Get (I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency);
SetNumTags (numTags);
SetNumRatchetInboundTags (numRatchetInboundTags);
pool->RequireLatency(minLatency, maxLatency);
return pool->Reconfigure(inLen, outLen, inQuant, outQuant);
}
std::shared_ptr<i2p::data::LeaseSet> LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
{
std::shared_ptr<i2p::data::LeaseSet> remoteLS;
{
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
auto it = m_RemoteLeaseSets.find (ident);
if (it != m_RemoteLeaseSets.end ())
remoteLS = it->second;
}
if (remoteLS)
{
if (!remoteLS->IsExpired ())
{
if (remoteLS->ExpiresSoon())
{
LogPrint(eLogDebug, "Destination: Lease Set expires soon, updating before expire");
// update now before expiration for smooth handover
auto s = shared_from_this ();
RequestDestination(ident, [s, ident] (std::shared_ptr<i2p::data::LeaseSet> ls) {
if(ls && !ls->IsExpired())
{
ls->PopulateLeases();
{
std::lock_guard<std::mutex> _lock(s->m_RemoteLeaseSetsMutex);
s->m_RemoteLeaseSets[ident] = ls;
}
}
});
}
return remoteLS;
}
else
{
LogPrint (eLogWarning, "Destination: Remote LeaseSet expired");
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
m_RemoteLeaseSets.erase (ident);
return nullptr;
}
}
return nullptr;
}
std::shared_ptr<const i2p::data::LocalLeaseSet> LeaseSetDestination::GetLeaseSet ()
{
if (!m_Pool) return nullptr;
if (!m_LeaseSet)
UpdateLeaseSet ();
auto ls = GetLeaseSetMt ();
return (ls && ls->GetInnerLeaseSet ()) ? ls->GetInnerLeaseSet () : ls; // always non-encrypted
}
std::shared_ptr<const i2p::data::LocalLeaseSet> LeaseSetDestination::GetLeaseSetMt ()
{
std::lock_guard<std::mutex> l(m_LeaseSetMutex);
return m_LeaseSet;
}
void LeaseSetDestination::SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet)
{
{
std::lock_guard<std::mutex> l(m_LeaseSetMutex);
m_LeaseSet = newLeaseSet;
}
i2p::garlic::GarlicDestination::SetLeaseSetUpdated ();
if (m_IsPublic)
{
auto s = shared_from_this ();
boost::asio::post (m_Service, [s](void)
{
s->m_PublishVerificationTimer.cancel ();
s->Publish ();
});
}
}
void LeaseSetDestination::UpdateLeaseSet ()
{
int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels
if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum
auto tunnels = m_Pool->GetInboundTunnels (numTunnels);
if (!tunnels.empty ())
CreateNewLeaseSet (tunnels);
else
LogPrint (eLogInfo, "Destination: No inbound tunnels for LeaseSet");
}
bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag)
{
struct
{
uint8_t k[32], t[32];
} data;
memcpy (data.k, key, 32);
memcpy (data.t, tag, 32);
auto s = shared_from_this ();
boost::asio::post (m_Service, [s,data](void)
{
s->AddSessionKey (data.k, data.t);
});
return true;
}
void LeaseSetDestination::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag)
{
struct
{
uint8_t k[32];
uint64_t t;
} data;
memcpy (data.k, key, 32);
data.t = tag;
auto s = shared_from_this ();
boost::asio::post (m_Service, [s,data](void)
{
s->AddECIESx25519Key (data.k, data.t);
});
}
void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
if (!msg) return;
bool empty = false;
{
std::lock_guard<std::mutex> l(m_IncomingMsgsQueueMutex);
empty = m_IncomingMsgsQueue.empty ();
m_IncomingMsgsQueue.push_back (msg);
}
if (empty)
boost::asio::post (m_Service, [s = shared_from_this ()]()
{
std::list<std::shared_ptr<I2NPMessage> > receivedMsgs;
{
std::lock_guard<std::mutex> l(s->m_IncomingMsgsQueueMutex);
s->m_IncomingMsgsQueue.swap (receivedMsgs);
}
for (auto& it: receivedMsgs)
s->HandleGarlicMessage (it);
});
}
void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
}
void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len)
{
I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]);
uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET);
LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE,
GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID, nullptr);
}
bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload,
size_t len, uint32_t msgID, i2p::garlic::ECIESX25519AEADRatchetSession * from)
{
switch (typeID)
{
case eI2NPData:
HandleDataMessage (payload, len, from);
break;
case eI2NPDeliveryStatus:
HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET));
break;
case eI2NPTunnelTest:
if (m_Pool)
m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
break;
case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (payload, len, from);
break;
case eI2NPDatabaseSearchReply:
HandleDatabaseSearchReplyMessage (payload, len);
break;
case eI2NPShortTunnelBuildReply: // might come as garlic encrypted
i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID));
break;
default:
LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID);
return false;
}
return true;
}
void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from)
{
if (len < DATABASE_STORE_HEADER_SIZE)
{
LogPrint (eLogError, "Destination: Database store msg is too short ", len);
return;
}
uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET);
size_t offset = DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore");
offset += 36;
}
if (offset > len || len > i2p::data::MAX_LS_BUFFER_SIZE + offset)
{
LogPrint (eLogError, "Destination: Database store message is too long ", len);
return;
}
i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET);
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
std::shared_ptr<LeaseSetRequest> request;
switch (buf[DATABASE_STORE_TYPE_OFFSET])
{
case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1
case i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2: // 3
{
LogPrint (eLogDebug, "Destination: Remote LeaseSet");
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
auto it = m_RemoteLeaseSets.find (key);
if (it != m_RemoteLeaseSets.end () &&
it->second->GetStoreType () == buf[DATABASE_STORE_TYPE_OFFSET]) // update only if same type
{
leaseSet = it->second;
if (leaseSet->IsNewer (buf + offset, len - offset))
{
leaseSet->Update (buf + offset, len - offset, shared_from_this(), true);
if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
LogPrint (eLogDebug, "Destination: Remote LeaseSet updated");
else
{
LogPrint (eLogDebug, "Destination: Remote LeaseSet update failed");
m_RemoteLeaseSets.erase (it);
leaseSet = nullptr;
}
}
else
LogPrint (eLogDebug, "Destination: Remote LeaseSet is older. Not updated");
}
else
{
// add or replace
if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET)
{
leaseSet = std::make_shared<i2p::data::LeaseSet> (buf + offset, len - offset); // LeaseSet
if (!SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL))
leaseSet->SetIsIncompatibleCrypto (true);
}
else
{
leaseSet = std::make_shared<i2p::data::LeaseSet2> (buf[DATABASE_STORE_TYPE_OFFSET],
buf + offset, len - offset, true, shared_from_this (),
from ? from->GetRemoteStaticKeyType () : GetPreferredCryptoType () ); // LeaseSet2
if (from)
{
uint8_t pub[32];
leaseSet->Encrypt (nullptr, pub);
if (!memcmp (from->GetRemoteStaticKey (), pub, 32))
from->SetDestination (leaseSet->GetIdentHash ());
else
{
LogPrint (eLogError, "Destination: Remote LeaseSet static key mismatch");
leaseSet = nullptr;
}
}
}
if (leaseSet && leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ())
{
if (leaseSet->GetIdentHash () != GetIdentHash ())
{
LogPrint (eLogDebug, "Destination: New remote LeaseSet added");
m_RemoteLeaseSets.insert_or_assign (key, leaseSet);
if (from)
from->SetDestination (key);
}
else
LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped");
}
else
{
LogPrint (eLogError, "Destination: New remote LeaseSet failed");
leaseSet = nullptr;
}
}
break;
}
case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5
{
auto it2 = m_LeaseSetRequests.find (key);
if (it2 != m_LeaseSetRequests.end ())
{
request = it2->second;
m_LeaseSetRequests.erase (it2);
if (request->requestedBlindedKey)
{
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
request->requestedBlindedKey, shared_from_this (),
m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr, GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ())
{
leaseSet = ls2;
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup
}
else
LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed");
}
else
{
// publishing verification doesn't have requestedBlindedKey
auto localLeaseSet = GetLeaseSetMt ();
if (localLeaseSet->GetStoreHash () == key)
{
auto ls = std::make_shared<i2p::data::LeaseSet2> (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2,
localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false);
leaseSet = ls;
}
else
LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key");
}
}
else
LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2");
break;
}
default:
LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
}
if (!request)
{
auto it1 = m_LeaseSetRequests.find (key);
if (it1 != m_LeaseSetRequests.end ())
{
request = it1->second;
m_LeaseSetRequests.erase (it1);
}
}
if (request)
{
request->requestTimeoutTimer.cancel ();
request->Complete (leaseSet);
}
}
void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len)
{
i2p::data::IdentHash key (buf);
int num = buf[32]; // num
LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num);
auto it = m_LeaseSetRequests.find (key);
if (it != m_LeaseSetRequests.end ())
{
auto request = it->second;
for (int i = 0; i < num; i++)
{
i2p::data::IdentHash peerHash (buf + 33 + i*32);
if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
{
LogPrint (eLogInfo, "Destination: Found new floodfill, request it");
i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory
}
}
SendNextLeaseSetRequest (key, request);
}
else
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
}
void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key,
std::shared_ptr<LeaseSetRequest> request)
{
bool found = false;
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
if (floodfill)
{
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
if (SendLeaseSetRequest (key, floodfill, request))
found = true;
}
}
if (!found)
{
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
request->Complete (nullptr);
m_LeaseSetRequests.erase (key);
}
}
void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID)
{
if (msgID == m_PublishReplyToken)
{
LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed for ", GetIdentHash().ToBase32());
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0;
// schedule verification
m_PublishVerificationTimer.expires_after (std::chrono::seconds(PUBLISH_VERIFICATION_TIMEOUT +
GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
}
else
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID);
}
void LeaseSetDestination::SetLeaseSetUpdated (bool post)
{
if (post)
boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); });
else
UpdateLeaseSet ();
}
void LeaseSetDestination::Publish ()
{
auto leaseSet = GetLeaseSetMt ();
if (!leaseSet || !m_Pool)
{
LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet");
return;
}
if (m_PublishReplyToken)
{
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is pending");
return;
}
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (ts < m_LastSubmissionTime + PUBLISH_MIN_INTERVAL)
{
LogPrint (eLogDebug, "Destination: Publishing LeaseSet is too fast. Wait for ", PUBLISH_MIN_INTERVAL, " seconds");
m_PublishDelayTimer.cancel ();
m_PublishDelayTimer.expires_after (std::chrono::seconds(PUBLISH_MIN_INTERVAL));
m_PublishDelayTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishDelayTimer,
shared_from_this (), std::placeholders::_1));
return;
}
auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
if (!floodfill)
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
m_ExcludedFloodfills.clear ();
return;
}
auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
if (!outbound || !inbound)
{
if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ())
{
LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill");
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
if (floodfill)
{
outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
if (outbound)
{
inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
if (!inbound)
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
}
else
LogPrint (eLogDebug, "Destination: No tunnels in pool");
if (!floodfill || !outbound || !inbound)
{
// we can't publish now
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 1; // dummy non-zero value
// try again after a while
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds");
m_PublishConfirmationTimer.expires_after (std::chrono::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
return;
}
}
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound));
auto s = shared_from_this ();
msg->onDrop = [s]()
{
boost::asio::post (s->GetService (), [s]()
{
s->m_PublishConfirmationTimer.cancel ();
s->HandlePublishConfirmationTimer (boost::system::error_code());
});
};
m_PublishConfirmationTimer.expires_after (std::chrono::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg);
m_LastSubmissionTime = ts;
}
void LeaseSetDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (m_PublishReplyToken)
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again");
m_PublishReplyToken = 0;
Publish ();
}
}
}
void LeaseSetDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ls = GetLeaseSetMt ();
if (!ls)
{
LogPrint (eLogWarning, "Destination: Couldn't verify LeaseSet for ", GetIdentHash().ToBase32());
return;
}
auto s = shared_from_this ();
RequestLeaseSet (ls->GetStoreHash (),
[s, ls](std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
{
if (leaseSet)
{
if (*ls == *leaseSet)
{
// we got latest LeasetSet
LogPrint (eLogDebug, "Destination: Published LeaseSet verified for ", s->GetIdentHash().ToBase32());
s->m_PublishVerificationTimer.expires_after (std::chrono::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL));
s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1));
return;
}
else
LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", s->GetIdentHash().ToBase32());
}
else
LogPrint (eLogWarning, "Destination: Couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32());
// we have to publish again
s->Publish ();
});
}
}
void LeaseSetDestination::HandlePublishDelayTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
Publish ();
}
bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete)
{
if (!m_Pool || !IsReady ())
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
return false;
}
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
return true;
}
bool LeaseSetDestination::RequestDestinationWithEncryptedLeaseSet (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, RequestComplete requestComplete)
{
if (!dest || !m_Pool || !IsReady ())
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
return false;
}
auto storeHash = dest->GetStoreHash ();
auto leaseSet = FindLeaseSet (storeHash);
if (leaseSet)
{
if (requestComplete)
boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);});
return true;
}
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
return true;
}
void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify)
{
auto s = shared_from_this ();
boost::asio::post (m_Service, [dest, notify, s](void)
{
auto it = s->m_LeaseSetRequests.find (dest);
if (it != s->m_LeaseSetRequests.end ())
{
auto requestComplete = it->second;
s->m_LeaseSetRequests.erase (it);
if (notify && requestComplete) requestComplete->Complete (nullptr);
}
});
}
void LeaseSetDestination::CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, bool notify)
{
if (dest)
CancelDestinationRequest (dest->GetStoreHash (), notify);
}
void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> requestedBlindedKey)
{
std::unordered_set<i2p::data::IdentHash> excluded;
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
if (floodfill)
{
auto request = std::make_shared<LeaseSetRequest> (m_Service);
request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2
if (requestComplete)
request->requestComplete.push_back (requestComplete);
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
if (ret.second) // inserted
{
request->requestTime = ts;
if (!SendLeaseSetRequest (dest, floodfill, request))
{
// try another
LogPrint (eLogWarning, "Destination: Couldn't send LeaseSet request to ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another");
request->excluded.insert (floodfill->GetIdentHash ());
floodfill = i2p::data::netdb.GetClosestFloodfill (dest, request->excluded);
if (!SendLeaseSetRequest (dest, floodfill, request))
{
// request failed
LogPrint (eLogWarning, "Destination: LeaseSet request for ", dest.ToBase32 (), " was not sent");
m_LeaseSetRequests.erase (ret.first);
if (requestComplete) requestComplete (nullptr);
}
}
}
else // duplicate
{
LogPrint (eLogInfo, "Destination: Request of LeaseSet ", dest.ToBase64 (), " is pending already");
if (ts > ret.first->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
// something went wrong
m_LeaseSetRequests.erase (ret.first);
if (requestComplete) requestComplete (nullptr);
}
else if (requestComplete)
ret.first->second->requestComplete.push_back (requestComplete);
}
}
else
{
LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found");
if (requestComplete) requestComplete (nullptr);
}
}
bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest,
std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request)
{
if (!request->replyTunnel || !request->replyTunnel->IsEstablished ())
request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); // outbound from floodfill
if (!request->replyTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible inbound tunnels found");
if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ())
request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); // inbound from floodfill
if (!request->outboundTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible outbound tunnels found");
if (request->replyTunnel && request->outboundTunnel)
{
request->excluded.insert (nextFloodfill->GetIdentHash ());
request->requestTimeoutTimer.cancel ();
uint8_t replyKey[32], replyTag[32];
RAND_bytes (replyKey, 32); // random session key
RAND_bytes (replyTag, 8); // random session tag
AddECIESx25519Key (replyKey, replyTag);
auto msg = WrapMessageForRouter (nextFloodfill,
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag));
auto s = shared_from_this ();
msg->onDrop = [s, dest, request]()
{
boost::asio::post (s->GetService (), [s, dest, request]()
{
s->SendNextLeaseSetRequest (dest, request);
});
};
request->outboundTunnel->SendTunnelDataMsgs (
{
i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeRouter,
nextFloodfill->GetIdentHash (), 0, msg
}
});
request->requestTimeoutTimer.expires_after (std::chrono::milliseconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
shared_from_this (), std::placeholders::_1, dest));
}
else
return false;
return true;
}
void LeaseSetDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto it = m_LeaseSetRequests.find (dest);
if (it != m_LeaseSetRequests.end ())
{
bool done = false;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
if (floodfill)
{
// reset tunnels, because one them might fail
it->second->outboundTunnel = nullptr;
it->second->replyTunnel = nullptr;
done = !SendLeaseSetRequest (dest, floodfill, it->second);
}
else
done = true;
}
else
{
LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds");
done = true;
}
if (done)
{
auto requestComplete = it->second;
m_LeaseSetRequests.erase (it);
if (requestComplete) requestComplete->Complete (nullptr);
}
}
}
}
void LeaseSetDestination::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
CleanupExpiredTags ();
CleanupRemoteLeaseSets ();
CleanupDestination ();
m_CleanupTimer.expires_after (std::chrono::seconds (DESTINATION_CLEANUP_TIMEOUT +
GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
}
void LeaseSetDestination::CleanupRemoteLeaseSets ()
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();)
{
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
{
LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
it = m_RemoteLeaseSets.erase (it);
}
else
++it;
}
}
ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const i2p::util::Mapping * params):
LeaseSetDestination (service, isPublic, params),
m_Keys (keys), m_PreferredCryptoType (0), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
m_StreamingMaxConnsPerMinute (DEFAULT_MAX_CONNS_PER_MINUTE),
m_StreamingMaxWindowSize (i2p::stream::MAX_WINDOW_SIZE),
m_StreamingMaxResends (i2p::stream::MAX_NUM_RESEND_ATTEMPTS),
m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_IsStreamingDontSign (DEFAULT_DONT_SIGN),
m_LastPort (0), m_RefCounter (0), m_LastPublishedTimestamp (0), m_ReadyChecker(service)
{
if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // offline keys can be published with LS2 only
// extract encryption type params for LS2
std::set<i2p::data::CryptoKeyType> encryptionKeyTypes;
if (params)
{
auto encryptionTypesStr = (*params)[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE];
if (!encryptionTypesStr.empty ())
{
// comma-separated values
std::vector<std::string> values;
boost::split(values, encryptionTypesStr, boost::is_any_of(","));
for (auto& it1: values)
{
try
{
i2p::data::CryptoKeyType cryptoType = std::stoi(it1);
#if !OPENSSL_PQ
if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported
#endif
{
if (!m_PreferredCryptoType && cryptoType)
m_PreferredCryptoType = cryptoType; // first non-zero in the list
encryptionKeyTypes.insert (cryptoType);
}
}
catch (std::exception& ex)
{
LogPrint (eLogInfo, "Destination: Unexpected crypto type ", it1, ". ", ex.what ());
continue;
}
}
}
}
// if no encryption type specified use 0,4 or 0,4,6 if post quantum
if (encryptionKeyTypes.empty ())
{
encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (),
#if OPENSSL_PQ
i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD,
#endif
i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD });
#if OPENSSL_PQ
m_PreferredCryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD;
#else
m_PreferredCryptoType = i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD;
#endif
}
for (auto& it: encryptionKeyTypes)
{
auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it);
if (IsPublic ())
PersistTemporaryKeys (encryptionKey);
else
encryptionKey->GenerateKeys ();
encryptionKey->CreateDecryptor ();
if (it > i2p::data::CRYPTO_KEY_TYPE_ELGAMAL && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Only DSA can use LeaseSet1
m_EncryptionKeys.emplace (it, encryptionKey);
}
if (IsPublic ())
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
try
{
if (params)
{
// extract streaming params
params->Get (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, m_StreamingAckDelay);
params->Get (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, m_StreamingOutboundSpeed);
params->Get (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, m_StreamingInboundSpeed);
params->Get (I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS, m_StreamingMaxConcurrentStreams);
params->Get (I2CP_PARAM_STREAMING_MAX_CONNS_PER_MINUTE, m_StreamingMaxConnsPerMinute);
if (params->Get (I2CP_PARAM_STREAMING_MAX_WINDOW_SIZE, m_StreamingMaxWindowSize) &&
(m_StreamingMaxWindowSize < i2p::stream::MIN_WINDOW_SIZE))
m_StreamingMaxWindowSize = i2p::stream::MIN_WINDOW_SIZE;
params->Get (I2CP_PARAM_STREAMING_ANSWER_PINGS, m_IsStreamingAnswerPings);
params->Get (I2CP_PARAM_STREAMING_DONT_SIGN, m_IsStreamingDontSign);
params->Get (I2CP_PARAM_STREAMING_MAX_RESENDS, m_StreamingMaxResends);
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
// authentication for encrypted LeaseSet
auto authType = GetAuthType ();
if (authType > 0)
{
m_AuthKeys = std::make_shared<std::vector<i2p::data::AuthPublicKey> >();
if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_DH)
ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params);
else if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK)
ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params);
else
LogPrint (eLogError, "Destination: Unexpected auth type: ", authType);
if (m_AuthKeys->size ())
LogPrint (eLogInfo, "Destination: ", m_AuthKeys->size (), " auth keys read");
else
{
LogPrint (eLogCritical, "Destination: No auth keys read for auth type: ", authType);
m_AuthKeys = nullptr;
}
}
}
}
}
catch (std::exception & ex)
{
LogPrint(eLogCritical, "Destination: Unable to parse parameters for destination: ", ex.what());
}
}
ClientDestination::~ClientDestination ()
{
}
void ClientDestination::Start ()
{
// Idempotent: a second Start() on a running destination must not re-create
// m_StreamingDestination (orphaning the live one) nor re-arm leaseset/pool.
if (m_StreamingDestination) return;
LeaseSetDestination::Start ();
m_StreamingDestination = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis ()); // TODO:
m_StreamingDestination->Start ();
for (auto& it: m_StreamingDestinationsByPorts)
it.second->Start ();
}
void ClientDestination::Stop ()
{
LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p");
m_ReadyChecker.cancel();
LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination");
m_StreamingDestination->Stop ();
//m_StreamingDestination->SetOwner (nullptr);
m_StreamingDestination = nullptr;
LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination by ports");
for (auto& it: m_StreamingDestinationsByPorts)
{
it.second->Stop ();
//it.second->SetOwner (nullptr);
}
m_StreamingDestinationsByPorts.clear ();
m_LastStreamingDestination = nullptr;
if (m_DatagramDestination)
{
LogPrint(eLogDebug, "Destination: -> Stopping Datagram Destination");
m_DatagramDestination = nullptr;
}
LeaseSetDestination::Stop ();
LogPrint(eLogDebug, "Destination: -> Stopping done");
}
void ClientDestination::SetPrivateKeys (const i2p::data::PrivateKeys& keys)
{
if (m_StreamingDestination) m_StreamingDestination->Stop (); // close all streams
CleanUp (); // delete sessions and tags
auto pool = GetTunnelPool ();
if (pool) pool->DetachTunnels ();
m_Keys = keys;
// update static keys
for (auto it: m_EncryptionKeys)
if (it.second)
{
it.second->GenerateKeys ();
it.second->CreateDecryptor ();
}
if (m_StreamingDestination) m_StreamingDestination->Start ();
}
void ClientDestination::UpdateOfflineSignature (const i2p::data::PrivateKeys& keys)
{
// Adopt a newer offline transient of the same identity in place: swap keys and
// republish the LeaseSet, keeping tunnels and streams (unlike SetPrivateKeys).
if (!keys.IsOfflineSignature () || !m_Keys.IsOfflineSignature ()) return;
if (keys.GetPublic ()->GetIdentHash () != GetIdentHash ()) return;
const uint32_t expires = bufbe32toh (keys.GetOfflineSignature ().data ());
if (expires <= bufbe32toh (m_Keys.GetOfflineSignature ().data ())) return; // only move forward
LogPrint (eLogInfo, "Destination: Refreshing offline signature for ",
GetIdentHash ().ToBase32 (), ", transient expires ", expires);
boost::asio::post (GetService (), [s = GetSharedFromThis (), keys]()
{
s->m_Keys = keys;
s->UpdateLeaseSet ();
});
}
void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len,
i2p::garlic::ECIESX25519AEADRatchetSession * from)
{
uint32_t length = bufbe32toh (buf);
if(length > len - 4)
{
LogPrint(eLogError, "Destination: Data message length ", length, " exceeds buffer length ", len);
return;
}
buf += 4;
// we assume I2CP payload
uint16_t fromPort = bufbe16toh (buf + 4), // source
toPort = bufbe16toh (buf + 6); // destination
switch (buf[9])
{
case PROTOCOL_TYPE_STREAMING:
{
// streaming protocol
if (toPort != m_LastPort || !m_LastStreamingDestination)
{
m_LastStreamingDestination = GetStreamingDestination (toPort);
if (!m_LastStreamingDestination)
m_LastStreamingDestination = m_StreamingDestination; // if no destination on port use default
m_LastPort = toPort;
}
if (m_LastStreamingDestination)
m_LastStreamingDestination->HandleDataMessagePayload (buf, length, from);
else
LogPrint (eLogError, "Destination: Missing streaming destination");
}
break;
case PROTOCOL_TYPE_DATAGRAM:
case PROTOCOL_TYPE_RAW:
case PROTOCOL_TYPE_DATAGRAM2:
case PROTOCOL_TYPE_DATAGRAM3:
// datagram protocol
if (m_DatagramDestination)
m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, buf[9], from);
else
LogPrint (eLogError, "Destination: Missing datagram destination");
break;
default:
LogPrint (eLogError, "Destination: Data: Unexpected protocol ", buf[9]);
}
}
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, uint16_t port)
{
if (!streamRequestComplete)
{
LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream");
return;
}
auto leaseSet = FindLeaseSet (dest);
if (leaseSet)
{
auto stream = (!leaseSet->IsIncompatibleCrypto ()) ? CreateStream (leaseSet, port) : nullptr;
boost::asio::post (GetService (), [streamRequestComplete, stream]()
{
streamRequestComplete(stream);
});
}
else
{
auto s = GetSharedFromThis ();
RequestDestination (dest,
[s, streamRequestComplete, port](std::shared_ptr<const i2p::data::LeaseSet> ls)
{
if (ls && !ls->IsIncompatibleCrypto ())
streamRequestComplete(s->CreateStream (ls, port));
else
streamRequestComplete (nullptr);
});
}
}
void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> dest, uint16_t port)
{
if (!streamRequestComplete)
{
LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream");
return;
}
auto s = GetSharedFromThis ();
RequestDestinationWithEncryptedLeaseSet (dest,
[s, streamRequestComplete, port](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls && !ls->IsIncompatibleCrypto ())
streamRequestComplete(s->CreateStream (ls, port));
else
streamRequestComplete (nullptr);
});
}
template<typename Dest>
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStreamSync (const Dest& dest, uint16_t port)
{
volatile bool done = false;
std::shared_ptr<i2p::stream::Stream> stream;
std::condition_variable streamRequestComplete;
std::mutex streamRequestCompleteMutex;
CreateStream (
[&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr<i2p::stream::Stream> s)
{
stream = s;
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
streamRequestComplete.notify_all ();
done = true;
},
dest, port);
while (!done)
{
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
if (!done)
streamRequestComplete.wait (l);
}
return stream;
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (const i2p::data::IdentHash& dest, uint16_t port)
{
return CreateStreamSync (dest, port);
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, uint16_t port)
{
return CreateStreamSync (dest, port);
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, uint16_t port)
{
if (m_StreamingDestination)
return m_StreamingDestination->CreateNewOutgoingStream (remote, port);
else
return nullptr;
}
void ClientDestination::SendPing (const i2p::data::IdentHash& to)
{
if (m_StreamingDestination)
{
auto leaseSet = FindLeaseSet (to);
if (leaseSet)
m_StreamingDestination->SendPing (leaseSet);
else
{
auto s = m_StreamingDestination;
RequestDestination (to,
[s](std::shared_ptr<const i2p::data::LeaseSet> ls)
{
if (ls) s->SendPing (ls);
});
}
}
}
void ClientDestination::SendPing (std::shared_ptr<const i2p::data::BlindedPublicKey> to)
{
auto s = m_StreamingDestination;
RequestDestinationWithEncryptedLeaseSet (to,
[s](std::shared_ptr<const i2p::data::LeaseSet> ls)
{
if (ls) s->SendPing (ls);
});
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::GetStreamingDestination (uint16_t port) const
{
if (port)
{
auto it = m_StreamingDestinationsByPorts.find (port);
if (it != m_StreamingDestinationsByPorts.end ())
return it->second;
}
else // if port is zero, use default destination
return m_StreamingDestination;
return nullptr;
}
void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor)
{
if (m_StreamingDestination)
m_StreamingDestination->SetAcceptor (acceptor);
}
void ClientDestination::StopAcceptingStreams ()
{
if (m_StreamingDestination)
m_StreamingDestination->ResetAcceptor ();
}
bool ClientDestination::IsAcceptingStreams () const
{
if (m_StreamingDestination)
return m_StreamingDestination->IsAcceptorSet ();
return false;
}
void ClientDestination::AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor)
{
if (m_StreamingDestination)
m_StreamingDestination->AcceptOnce (acceptor);
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::CreateStreamingDestination (uint16_t port, bool gzip)
{
auto dest = std::make_shared<i2p::stream::StreamingDestination> (GetSharedFromThis (), port, gzip);
if (port)
m_StreamingDestinationsByPorts[port] = dest;
else // update default
m_StreamingDestination = dest;
return dest;
}
std::shared_ptr<i2p::stream::StreamingDestination> ClientDestination::RemoveStreamingDestination (uint16_t port)
{
if (port)
{
auto it = m_StreamingDestinationsByPorts.find (port);
if (it != m_StreamingDestinationsByPorts.end ())
{
auto ret = it->second;
m_StreamingDestinationsByPorts.erase (it);
return ret;
}
}
return nullptr;
}
std::shared_ptr<i2p::datagram::DatagramDestination> ClientDestination::CreateDatagramDestination (bool gzip,
i2p::datagram::DatagramVersion version)
{
if (!m_DatagramDestination)
{
if (!GetNumRatchetInboundTags ())
SetNumRatchetInboundTags (i2p::garlic::ECIESX25519_MAX_NUM_GENERATED_TAGS); // set max tags if not specified
m_DatagramDestination = std::make_shared<i2p::datagram::DatagramDestination> (GetSharedFromThis (), gzip, version);
}
return m_DatagramDestination;
}
std::vector<std::shared_ptr<const i2p::stream::Stream> > ClientDestination::GetAllStreams () const
{
std::vector<std::shared_ptr<const i2p::stream::Stream> > ret;
if (m_StreamingDestination)
{
for (auto& it: m_StreamingDestination->GetStreams ())
ret.push_back (it.second);
}
for (auto& it: m_StreamingDestinationsByPorts)
for (auto& it1: it.second->GetStreams ())
ret.push_back (it1.second);
return ret;
}
void ClientDestination::PersistTemporaryKeys (std::shared_ptr<i2p::crypto::LocalEncryptionKey> keys)
{
if (!keys) return;
std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat");
std::ifstream f(path, std::ifstream::binary);
if (f)
{
size_t len = 0;
if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
len = 512;
else if (keys->keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
f.seekg (0, std::ios::end);
len = f.tellg();
f.seekg (0, std::ios::beg);
}
if (len == 512)
{
char pub[256], priv[256];
f.read (pub, 256);
memcpy (keys->pub.data(), pub, keys->pub.size());
f.read (priv, 256);
memcpy (keys->priv.data (), priv, keys->priv.size ());
}
else
{
f.read ((char *)keys->pub.data(), keys->pub.size());
f.read ((char *)keys->priv.data(), keys->priv.size());
}
if (f)
return;
else
LogPrint(eLogWarning, "Destination: Can't read keys from ", path);
}
LogPrint (eLogInfo, "Destination: Creating new temporary keys of type ", keys->keyType, " for address ", ident, ".b32.i2p");
memset (keys->priv.data (), 0, keys->priv.size ());
memset (keys->pub.data (), 0, keys->pub.size ());
keys->GenerateKeys ();
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1)
{
f1.write ((char *)keys->pub.data (), keys->pub.size ());
f1.write ((char *)keys->priv.data (), keys->priv.size ());
}
if (!f1)
LogPrint(eLogError, "Destination: Can't save keys to ", path);
}
void ClientDestination::CreateNewLeaseSet (const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels)
{
std::shared_ptr<i2p::data::LocalLeaseSet> leaseSet;
if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
{
auto it = m_EncryptionKeys.find (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL);
if (it != m_EncryptionKeys.end ())
{
leaseSet = std::make_shared<i2p::data::LocalLeaseSet> (GetIdentity (), it->second->pub.data (), tunnels);
// sign
Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ());
}
else
LogPrint (eLogError, "Destinations: Wrong encryption key type for LeaseSet type 1");
}
else
{
// standard LS2 (type 3) first
if (m_EncryptionKeys.empty ())
{
LogPrint (eLogError, "Destinations: No encryption keys");
return;
}
i2p::data::LocalLeaseSet2::EncryptionKeys keySections;
std::shared_ptr<const i2p::crypto::LocalEncryptionKey> preferredSection;
if (m_EncryptionKeys.size () == 1)
preferredSection = m_EncryptionKeys.begin ()->second; // only key
else
{
for (const auto& it: m_EncryptionKeys)
if (it.first == m_PreferredCryptoType)
preferredSection = it.second;
else
keySections.push_front (it.second); // higher key type should appear first
}
if (preferredSection)
keySections.push_front (preferredSection); // make preferred first
auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch ();
if (publishedTimestamp <= m_LastPublishedTimestamp)
{
LogPrint (eLogDebug, "Destination: LeaseSet update at the same second");
publishedTimestamp++; // force newer timestamp
}
bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
auto ls2 = std::make_shared<i2p::data::LocalLeaseSet2> (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted);
if (isPublishedEncrypted) // encrypt if type 5
ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys, GetAuthType (), m_AuthKeys);
leaseSet = ls2;
m_LastPublishedTimestamp = publishedTimestamp;
}
SetLeaseSet (leaseSet);
}
void ClientDestination::CleanupDestination ()
{
if (m_DatagramDestination) m_DatagramDestination->CleanUp ();
}
bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
{
std::shared_ptr<i2p::crypto::LocalEncryptionKey> encryptionKey;
if (!m_EncryptionKeys.empty ())
{
if (m_EncryptionKeys.rbegin ()->first == preferredCrypto)
encryptionKey = m_EncryptionKeys.rbegin ()->second;
else
{
auto it = m_EncryptionKeys.find (preferredCrypto);
if (it != m_EncryptionKeys.end ())
encryptionKey = it->second;
}
if (!encryptionKey)
encryptionKey = m_EncryptionKeys.rbegin ()->second;
}
if (encryptionKey)
return encryptionKey->decryptor->Decrypt (encrypted, data);
else
LogPrint (eLogError, "Destinations: Decryptor is not set");
return false;
}
bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const
{
#if __cplusplus >= 202002L // C++20
return m_EncryptionKeys.contains (keyType);
#else
return m_EncryptionKeys.count (keyType) > 0;
#endif
}
i2p::data::CryptoKeyType ClientDestination::GetRatchetsHighestCryptoType () const
{
if (m_EncryptionKeys.empty ()) return 0;
auto cryptoType = m_EncryptionKeys.rbegin ()->first;
return cryptoType >= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? cryptoType : 0;
}
const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const
{
auto it = m_EncryptionKeys.find (keyType);
if (it != m_EncryptionKeys.end ())
return it->second->pub.data ();
return nullptr;
}
void ClientDestination::ReadAuthKey (const std::string& group, const i2p::util::Mapping * params)
{
for (const auto& it: params->GetOptions ())
if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group))
{
auto pos = it.second.find (':');
if (pos != std::string::npos)
{
i2p::data::AuthPublicKey pubKey;
if (pubKey.FromBase64 (it.second.substr (pos+1)))
m_AuthKeys->push_back (pubKey);
else
LogPrint (eLogCritical, "Destination: Unexpected auth key: ", it.second.substr (pos+1));
}
}
}
bool ClientDestination::DeleteStream (uint32_t recvStreamID)
{
if (m_StreamingDestination->DeleteStream (recvStreamID))
return true;
for (auto it: m_StreamingDestinationsByPorts)
if (it.second->DeleteStream (recvStreamID))
return true;
return false;
}
RunnableClientDestination::RunnableClientDestination (const i2p::data::PrivateKeys& keys,
bool isPublic, const i2p::util::Mapping * params):
RunnableService ("Destination"), ClientDestination (GetIOService (), keys, isPublic, params)
{
if (!GetNickname ().empty ())
RunnableService::SetName (GetNickname ());
}
RunnableClientDestination::~RunnableClientDestination ()
{
if (IsRunning ())
Stop ();
}
void RunnableClientDestination::Start ()
{
if (!IsRunning ())
{
ClientDestination::Start ();
StartIOService ();
}
}
void RunnableClientDestination::Stop ()
{
if (IsRunning ())
{
ClientDestination::Stop ();
StopIOService ();
}
}
}
}