/* * Copyright (c) 2013-2022, 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 #include #include #include #include #include #include "Crypto.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_service &service, bool isPublic, const std::map *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; std::shared_ptr > explicitPeers; try { if (params) { auto it = params->find(I2CP_PARAM_INBOUND_TUNNEL_LENGTH); if (it != params->end()) inLen = std::stoi(it->second); it = params->find(I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); if (it != params->end()) outLen = std::stoi(it->second); it = params->find(I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); if (it != params->end()) inQty = std::stoi(it->second); it = params->find(I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); if (it != params->end()) outQty = std::stoi(it->second); it = params->find(I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE); if (it != params->end()) inVar = std::stoi(it->second); it = params->find(I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE); if (it != params->end()) outVar = std::stoi(it->second); it = params->find(I2CP_PARAM_TAGS_TO_SEND); if (it != params->end()) numTags = std::stoi(it->second); LogPrint(eLogInfo, "Destination: Parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); it = params->find(I2CP_PARAM_RATCHET_INBOUND_TAGS); if (it != params->end()) SetNumRatchetInboundTags(std::stoi(it->second)); it = params->find(I2CP_PARAM_EXPLICIT_PEERS); if (it != params->end()) { explicitPeers = std::make_shared >(); std::stringstream ss(it->second); std::string b64; while (std::getline(ss, b64, ',')) { i2p::data::IdentHash ident; ident.FromBase64(b64); explicitPeers->push_back(ident); LogPrint(eLogInfo, "Destination: Added to explicit peers list: ", b64); } } it = params->find(I2CP_PARAM_INBOUND_NICKNAME); if (it != params->end()) m_Nickname = it->second; else // try outbound { it = params->find(I2CP_PARAM_OUTBOUND_NICKNAME); if (it != params->end()) m_Nickname = it->second; // otherwise we set default nickname in Start when we know local address } it = params->find(I2CP_PARAM_DONT_PUBLISH_LEASESET); if (it != params->end()) { // oveeride isPublic m_IsPublic = (it->second != "true"); } it = params->find(I2CP_PARAM_LEASESET_TYPE); if (it != params->end()) m_LeaseSetType = std::stoi(it->second); if (m_LeaseSetType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet it = params->find(I2CP_PARAM_LEASESET_AUTH_TYPE); if (it != params->end()) { auto authType = std::stoi(it->second); 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); } } it = params->find(I2CP_PARAM_LEASESET_PRIV_KEY); if (it != params->end()) { m_LeaseSetPrivKey.reset(new i2p::data::Tag<32>()); if (m_LeaseSetPrivKey->FromBase64(it->second) != 32) { LogPrint(eLogError, "Destination: Invalid value i2cp.leaseSetPrivKey ", it->second); m_LeaseSetPrivKey.reset(nullptr); } } } } 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); if (explicitPeers) m_Pool->SetExplicitPeers(explicitPeers); if (params) { auto itr = params->find(I2CP_PARAM_MAX_TUNNEL_LATENCY); if (itr != params->end()) { auto maxlatency = std::stoi(itr->second); itr = params->find(I2CP_PARAM_MIN_TUNNEL_LATENCY); if (itr != params->end()) { auto minlatency = std::stoi(itr->second); 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_from_now(boost::posix_time::minutes(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(std::map params) { auto itr = params.find("i2cp.dontPublishLeaseSet"); if (itr != params.end()) { m_IsPublic = itr->second != "true"; } int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency; std::map intOpts = { {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, {I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQuant}, {I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQuant}, {I2CP_PARAM_TAGS_TO_SEND, numTags}, {I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency}, {I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency} }; auto pool = GetTunnelPool(); inLen = pool->GetNumInboundHops(); outLen = pool->GetNumOutboundHops(); inQuant = pool->GetNumInboundTunnels(); outQuant = pool->GetNumOutboundTunnels(); minLatency = 0; maxLatency = 0; for (auto &opt: intOpts) { itr = params.find(opt.first); if (itr != params.end()) { opt.second = std::stoi(itr->second); } } pool->RequireLatency(minLatency, maxLatency); return pool->Reconfigure(inLen, outLen, inQuant, outQuant); } std::shared_ptr LeaseSetDestination::FindLeaseSet(const i2p::data::IdentHash &ident) { std::shared_ptr remoteLS; { std::lock_guard 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 ls) { if (ls && !ls->IsExpired()) { ls->PopulateLeases(); { std::lock_guard _lock(s->m_RemoteLeaseSetsMutex); s->m_RemoteLeaseSets[ident] = ls; } } }); } return remoteLS; } else { LogPrint(eLogWarning, "Destination: Remote LeaseSet expired"); std::lock_guard lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets.erase(ident); return nullptr; } } else { auto ls = i2p::data::netdb.FindLeaseSet(ident); if (ls && !ls->IsExpired()) { ls->PopulateLeases(); // since we don't store them in netdb std::lock_guard _lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; return ls; } } return nullptr; } std::shared_ptr 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 LeaseSetDestination::GetLeaseSetMt() { std::lock_guard l(m_LeaseSetMutex); return m_LeaseSet; } void LeaseSetDestination::SetLeaseSet(std::shared_ptr newLeaseSet) { { std::lock_guard l(m_LeaseSetMutex); m_LeaseSet = newLeaseSet; } i2p::garlic::GarlicDestination::SetLeaseSetUpdated(); if (m_IsPublic) { auto s = shared_from_this(); m_Service.post([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(); m_Service.post([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(); m_Service.post([s, data](void) { s->AddECIESx25519Key(data.k, data.t); }); } void LeaseSetDestination::ProcessGarlicMessage(std::shared_ptr msg) { m_Service.post(std::bind(&LeaseSetDestination::HandleGarlicMessage, shared_from_this(), msg)); } void LeaseSetDestination::ProcessDeliveryStatusMessage(std::shared_ptr msg) { uint32_t msgID = bufbe32toh(msg->GetPayload() + DELIVERY_STATUS_MSGID_OFFSET); m_Service.post(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); } bool LeaseSetDestination::HandleCloveI2NPMessage(I2NPMessageType typeID, const uint8_t *payload, size_t len, uint32_t msgID) { switch (typeID) { case eI2NPData: HandleDataMessage(payload, len); break; case eI2NPDeliveryStatus: // we assume tunnel tests non-encrypted HandleDeliveryStatusMessage(bufbe32toh(payload + DELIVERY_STATUS_MSGID_OFFSET)); break; case eI2NPDatabaseStore: HandleDatabaseStoreMessage(payload, len); 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) { 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; } i2p::data::IdentHash key(buf + DATABASE_STORE_KEY_OFFSET); std::shared_ptr leaseSet; 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 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); 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(buf + offset, len - offset); // LeaseSet else leaseSet = std::make_shared(buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType()); // LeaseSet2 if (leaseSet->IsValid() && leaseSet->GetIdentHash() == key && !leaseSet->IsExpired()) { if (leaseSet->GetIdentHash() != GetIdentHash()) { LogPrint(eLogDebug, "Destination: New remote LeaseSet added"); m_RemoteLeaseSets[key] = leaseSet; } 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() && it2->second->requestedBlindedKey) { auto ls2 = std::make_shared(buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *) *m_LeaseSetPrivKey) : nullptr, GetPreferredCryptoType()); if (ls2->IsValid()) { m_RemoteLeaseSets[ls2->GetIdentHash()] = ls2; // ident is not key m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup leaseSet = ls2; } } else LogPrint(eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2"); break; } default: LogPrint(eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped"); } auto it1 = m_LeaseSetRequests.find(key); if (it1 != m_LeaseSetRequests.end()) { it1->second->requestTimeoutTimer.cancel(); if (it1->second) it1->second->Complete(leaseSet); m_LeaseSetRequests.erase(it1); } } 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; bool found = false; if (request->excluded.size() < MAX_NUM_FLOODFILLS_PER_REQUEST) { 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 } } 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); } } else LogPrint(eLogWarning, "Destination: Request for ", key.ToBase64(), " not found"); } 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_from_now(boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait(std::bind(&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this(), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage(msgID); } void LeaseSetDestination::SetLeaseSetUpdated() { 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_from_now(boost::posix_time::seconds(PUBLISH_MIN_INTERVAL)); m_PublishDelayTimer.async_wait(std::bind(&LeaseSetDestination::HandlePublishDelayTimer, shared_from_this(), std::placeholders::_1)); return; } if (!m_Pool->GetInboundTunnels().size() || !m_Pool->GetOutboundTunnels().size()) { LogPrint(eLogError, "Destination: Can't publish LeaseSet. Destination is not ready"); return; } auto floodfill = i2p::data::netdb.GetClosestFloodfill(leaseSet->GetIdentHash(), 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) { LogPrint(eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash().ToBase64(), ". Trying another floodfill"); m_ExcludedFloodfills.insert(floodfill->GetIdentHash()); floodfill = i2p::data::netdb.GetClosestFloodfill(leaseSet->GetIdentHash(), 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"); if (!floodfill || !outbound || !inbound) { m_ExcludedFloodfills.clear(); 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)); m_PublishConfirmationTimer.expires_from_now(boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait(std::bind(&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this(), std::placeholders::_1)); outbound->SendTunnelDataMsg(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) { m_PublishReplyToken = 0; if (GetIdentity()->GetCryptoKeyType() == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { LogPrint(eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); Publish(); } else { LogPrint(eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int) GetIdentity()->GetCryptoKeyType()); // Java floodfill never sends confirmation back for unknown crypto type // assume it successive and try to verify m_PublishVerificationTimer.expires_from_now( boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait( std::bind(&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this(), std::placeholders::_1)); } } } } 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 leaseSet) { if (leaseSet) { if (*ls == *leaseSet) { // we got latest LeasetSet LogPrint(eLogDebug, "Destination: Published LeaseSet verified for ", s->GetIdentHash().ToBase32()); s->m_PublishVerificationTimer.expires_from_now( boost::posix_time::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) m_Service.post([requestComplete](void) { requestComplete(nullptr); }); return false; } m_Service.post(std::bind(&LeaseSetDestination::RequestLeaseSet, shared_from_this(), dest, requestComplete, nullptr)); return true; } bool LeaseSetDestination::RequestDestinationWithEncryptedLeaseSet( std::shared_ptr dest, RequestComplete requestComplete) { if (!dest || !m_Pool || !IsReady()) { if (requestComplete) m_Service.post([requestComplete](void) { requestComplete(nullptr); }); return false; } auto storeHash = dest->GetStoreHash(); auto leaseSet = FindLeaseSet(storeHash); if (leaseSet) { if (requestComplete) m_Service.post([requestComplete, leaseSet](void) { requestComplete(leaseSet); }); return true; } m_Service.post( 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(); m_Service.post([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 dest, bool notify) { if (dest) CancelDestinationRequest(dest->GetStoreHash(), notify); } void LeaseSetDestination::RequestLeaseSet(const i2p::data::IdentHash &dest, RequestComplete requestComplete, std::shared_ptr requestedBlindedKey) { std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill(dest, excluded); if (floodfill) { auto request = std::make_shared(m_Service); request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2 if (requestComplete) request->requestComplete.push_back(requestComplete); auto ts = i2p::util::GetSecondsSinceEpoch(); auto ret = m_LeaseSetRequests.insert( std::pair >(dest, request)); if (ret.second) // inserted { request->requestTime = ts; if (!SendLeaseSetRequest(dest, floodfill, request)) { // request failed 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 nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished()) request->replyTunnel = m_Pool->GetNextInboundTunnel(nullptr, nextFloodfill->GetCompatibleTransports(true)); if (!request->replyTunnel) LogPrint(eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished()) request->outboundTunnel = m_Pool->GetNextOutboundTunnel(nullptr, nextFloodfill->GetCompatibleTransports(false)); if (!request->outboundTunnel) LogPrint(eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) { request->excluded.insert(nextFloodfill->GetIdentHash()); request->requestTimeoutTimer.cancel(); bool isECIES = SupportsEncryptionType(i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) && nextFloodfill->GetVersion() >= MAKE_VERSION_NUMBER(0, 9, 46); // >= 0.9.46; uint8_t replyKey[32], replyTag[32]; RAND_bytes(replyKey, 32); // random session key RAND_bytes(replyTag, isECIES ? 8 : 32); // random session tag if (isECIES) AddECIESx25519Key(replyKey, replyTag); else AddSessionKey(replyKey, replyTag); auto msg = WrapMessageForRouter(nextFloodfill, CreateLeaseSetDatabaseLookupMsg(dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); request->outboundTunnel->SendTunnelDataMsg( { i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, nextFloodfill->GetIdentHash(), 0, msg } }); request->requestTimeoutTimer.expires_from_now(boost::posix_time::seconds(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::GetSecondsSinceEpoch(); 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_from_now(boost::posix_time::minutes(DESTINATION_CLEANUP_TIMEOUT)); 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 lock(m_RemoteLeaseSetsMutex); for (auto it = m_RemoteLeaseSets.begin(); it != m_RemoteLeaseSets.end();) { if (it->second->IsEmpty() || ts > it->second->GetExpirationTime()) // leaseset expired { LogPrint(eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash().ToBase64(), " expired"); it = m_RemoteLeaseSets.erase(it); } else ++it; } } i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType() const { if (SupportsEncryptionType(i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; } ClientDestination::ClientDestination(boost::asio::io_service &service, const i2p::data::PrivateKeys &keys, bool isPublic, const std::map *params) : LeaseSetDestination(service, isPublic, params), m_Keys(keys), m_StreamingAckDelay(DEFAULT_INITIAL_ACK_DELAY), m_IsStreamingAnswerPings(DEFAULT_ANSWER_PINGS), m_DatagramDestination(nullptr), m_RefCounter(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 encryptionKeyTypes; if (params) { auto it = params->find(I2CP_PARAM_LEASESET_ENCRYPTION_TYPE); if (it != params->end()) { // comma-separated values std::vector values; boost::split(values, it->second, boost::is_any_of(",")); for (auto &it1: values) { try { encryptionKeyTypes.insert(std::stoi(it1)); } catch (std::exception &ex) { LogPrint(eLogInfo, "Destination: Unexpected crypto type ", it1, ". ", ex.what()); continue; } } } } // if no param or valid crypto type use from identity bool isSingleKey = false; if (encryptionKeyTypes.empty()) { isSingleKey = true; encryptionKeyTypes.insert(GetIdentity()->GetCryptoKeyType()); } for (auto &it: encryptionKeyTypes) { auto encryptionKey = new EncryptionKey(it); if (IsPublic()) PersistTemporaryKeys(encryptionKey, isSingleKey); else encryptionKey->GenerateKeys(); encryptionKey->CreateDecryptor(); if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { m_ECIESx25519EncryptionKey.reset(encryptionKey); if (GetLeaseSetType() == i2p::data::NETDB_STORE_TYPE_LEASESET) SetLeaseSetType(i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2 } else m_StandardEncryptionKey.reset(encryptionKey); } if (IsPublic()) LogPrint(eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32(), " created"); try { if (params) { // extract streaming params auto it = params->find(I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); if (it != params->end()) m_StreamingAckDelay = std::stoi(it->second); it = params->find(I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end()) m_IsStreamingAnswerPings = (it->second == "true"); if (GetLeaseSetType() == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet auto authType = GetAuthType(); if (authType > 0) { m_AuthKeys = std::make_shared >(); 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(eLogError, "Destination: No auth keys read for auth type ", authType); m_AuthKeys = nullptr; } } } } } catch (std::exception &ex) { LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } } ClientDestination::~ClientDestination() { } void ClientDestination::Start() { LeaseSetDestination::Start(); m_StreamingDestination = std::make_shared(GetSharedFromThis()); // TODO: m_StreamingDestination->Start(); for (auto &it: m_StreamingDestinationsByPorts) it.second->Start(); } void ClientDestination::Stop() { LeaseSetDestination::Stop(); m_ReadyChecker.cancel(); m_StreamingDestination->Stop(); //m_StreamingDestination->SetOwner (nullptr); m_StreamingDestination = nullptr; for (auto &it: m_StreamingDestinationsByPorts) { it.second->Stop(); //it.second->SetOwner (nullptr); } m_StreamingDestinationsByPorts.clear(); if (m_DatagramDestination) { delete m_DatagramDestination; m_DatagramDestination = nullptr; } } void ClientDestination::HandleDataMessage(const uint8_t *buf, size_t len) { 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 auto dest = GetStreamingDestination(toPort); if (dest) dest->HandleDataMessagePayload(buf, length); else LogPrint(eLogError, "Destination: Missing streaming destination"); } break; case PROTOCOL_TYPE_DATAGRAM: // datagram protocol if (m_DatagramDestination) m_DatagramDestination->HandleDataMessagePayload(fromPort, toPort, buf, length); else LogPrint(eLogError, "Destination: Missing datagram destination"); break; case PROTOCOL_TYPE_RAW: // raw datagram if (m_DatagramDestination) m_DatagramDestination->HandleDataMessagePayload(fromPort, toPort, buf, length, true); else LogPrint(eLogError, "Destination: Missing raw datagram destination"); break; default: LogPrint(eLogError, "Destination: Data: Unexpected protocol ", buf[9]); } } void ClientDestination::CreateStream(StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash &dest, int port) { if (!streamRequestComplete) { LogPrint(eLogError, "Destination: Request callback is not specified in CreateStream"); return; } auto leaseSet = FindLeaseSet(dest); if (leaseSet) streamRequestComplete(CreateStream(leaseSet, port)); else { auto s = GetSharedFromThis(); RequestDestination(dest, [s, streamRequestComplete, port](std::shared_ptr ls) { if (ls) streamRequestComplete(s->CreateStream(ls, port)); else streamRequestComplete(nullptr); }); } } void ClientDestination::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int 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 ls) { if (ls) streamRequestComplete(s->CreateStream(ls, port)); else streamRequestComplete(nullptr); }); } std::shared_ptr ClientDestination::CreateStream(std::shared_ptr remote, int 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 ls) { if (ls) s->SendPing(ls); }); } } } void ClientDestination::SendPing(std::shared_ptr to) { auto s = m_StreamingDestination; RequestDestinationWithEncryptedLeaseSet(to, [s](std::shared_ptr ls) { if (ls) s->SendPing(ls); }); } std::shared_ptr ClientDestination::GetStreamingDestination(int port) const { if (port) { auto it = m_StreamingDestinationsByPorts.find(port); if (it != m_StreamingDestinationsByPorts.end()) return it->second; } // if port is zero or not found, use default destination return m_StreamingDestination; } 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 ClientDestination::CreateStreamingDestination(int port, bool gzip) { auto dest = std::make_shared(GetSharedFromThis(), port, gzip); if (port) m_StreamingDestinationsByPorts[port] = dest; else // update default m_StreamingDestination = dest; return dest; } std::shared_ptr ClientDestination::RemoveStreamingDestination(int 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; } i2p::datagram::DatagramDestination *ClientDestination::CreateDatagramDestination(bool gzip) { if (m_DatagramDestination == nullptr) m_DatagramDestination = new i2p::datagram::DatagramDestination(GetSharedFromThis(), gzip); return m_DatagramDestination; } std::vector > ClientDestination::GetAllStreams() const { std::vector > 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(EncryptionKey *keys, bool isSingleKey) { if (!keys) return; std::string ident = GetIdentHash().ToBase32(); std::string path = i2p::fs::DataDirPath("destinations", isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string(keys->keyType) + ".dat")); std::ifstream f(path, std::ifstream::binary); if (f) { f.read((char *) keys->pub, 256); f.read((char *) keys->priv, 256); return; } LogPrint(eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p"); memset(keys->priv, 0, 256); memset(keys->pub, 0, 256); keys->GenerateKeys(); // TODO:: persist crypto key type std::ofstream f1(path, std::ofstream::binary | std::ofstream::out); if (f1) { f1.write((char *) keys->pub, 256); f1.write((char *) keys->priv, 256); return; } LogPrint(eLogError, "Destinations: Can't save keys to ", path); } void ClientDestination::CreateNewLeaseSet(const std::vector > &tunnels) { std::shared_ptr leaseSet; if (GetLeaseSetType() == i2p::data::NETDB_STORE_TYPE_LEASESET) { if (m_StandardEncryptionKey) { leaseSet = std::make_shared(GetIdentity(), m_StandardEncryptionKey->pub, 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 i2p::data::LocalLeaseSet2::KeySections keySections; if (m_ECIESx25519EncryptionKey) keySections.push_back({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub}); if (m_StandardEncryptionKey) keySections.push_back({m_StandardEncryptionKey->keyType, (uint16_t) m_StandardEncryptionKey->decryptor->GetPublicKeyLen(), m_StandardEncryptionKey->pub}); bool isPublishedEncrypted = GetLeaseSetType() == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto ls2 = std::make_shared(i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, m_Keys, keySections, tunnels, IsPublic(), isPublishedEncrypted); if (isPublishedEncrypted) // encrypt if type 5 ls2 = std::make_shared(ls2, m_Keys, GetAuthType(), m_AuthKeys); leaseSet = ls2; } 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 { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) return m_ECIESx25519EncryptionKey->decryptor->Decrypt(encrypted, data); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) return m_StandardEncryptionKey->decryptor->Decrypt(encrypted, data); else LogPrint(eLogError, "Destinations: Decryptor is not set"); return false; } bool ClientDestination::SupportsEncryptionType(i2p::data::CryptoKeyType keyType) const { return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool) m_ECIESx25519EncryptionKey : (bool) m_StandardEncryptionKey; } const uint8_t *ClientDestination::GetEncryptionPublicKey(i2p::data::CryptoKeyType keyType) const { if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; } void ClientDestination::ReadAuthKey(const std::string &group, const std::map *params) { for (auto it: *params) 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(eLogError, "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 std::map *params) : RunnableService("Destination"), ClientDestination(GetIOService(), keys, isPublic, params) { } RunnableClientDestination::~RunnableClientDestination() { if (IsRunning()) Stop(); } void RunnableClientDestination::Start() { if (!IsRunning()) { ClientDestination::Start(); StartIOService(); } } void RunnableClientDestination::Stop() { if (IsRunning()) { ClientDestination::Stop(); StopIOService(); } } } }