mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2026-07-02 09:12:02 +00:00
acc0f94e49
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.
1663 lines
57 KiB
C++
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 ();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|