NetDbRequests.cpp
1 /* 2 * Copyright (c) 2013-2025, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include "Log.h" 10 #include "I2NPProtocol.h" 11 #include "Transports.h" 12 #include "NetDb.hpp" 13 #include "ECIESX25519AEADRatchetSession.h" 14 #include "RouterContext.h" 15 #include "Timestamp.h" 16 #include "NetDbRequests.h" 17 18 namespace i2p 19 { 20 namespace data 21 { 22 RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct): 23 m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), 24 m_IsActive (true), m_IsSentDirectly (false), 25 m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()), 26 m_LastRequestTime (0), m_NumAttempts (0) 27 { 28 if (i2p::context.IsFloodfill ()) 29 m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill 30 } 31 32 RequestedDestination::~RequestedDestination () 33 { 34 InvokeRequestComplete (nullptr); 35 } 36 37 std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router, 38 std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel) 39 { 40 std::shared_ptr<I2NPMessage> msg; 41 if(replyTunnel) 42 msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, 43 replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, 44 &m_ExcludedPeers); 45 else 46 msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); 47 if(router) 48 m_ExcludedPeers.insert (router->GetIdentHash ()); 49 m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); 50 m_NumAttempts++; 51 m_IsSentDirectly = false; 52 return msg; 53 } 54 55 std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) 56 { 57 auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, 58 i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); 59 m_ExcludedPeers.insert (floodfill); 60 m_NumAttempts++; 61 m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch (); 62 m_IsSentDirectly = true; 63 return msg; 64 } 65 66 bool RequestedDestination::IsExcluded (const IdentHash& ident) const 67 { 68 return m_ExcludedPeers.count (ident); 69 } 70 71 void RequestedDestination::ClearExcludedPeers () 72 { 73 m_ExcludedPeers.clear (); 74 } 75 76 void RequestedDestination::InvokeRequestComplete (std::shared_ptr<RouterInfo> r) 77 { 78 if (!m_RequestComplete.empty ()) 79 { 80 for (auto it: m_RequestComplete) 81 if (it != nullptr) it (r); 82 m_RequestComplete.clear (); 83 } 84 } 85 86 void RequestedDestination::Success (std::shared_ptr<RouterInfo> r) 87 { 88 if (m_IsActive) 89 { 90 m_IsActive = false; 91 InvokeRequestComplete (r); 92 } 93 } 94 95 void RequestedDestination::Fail () 96 { 97 if (m_IsActive) 98 { 99 m_IsActive = false; 100 InvokeRequestComplete (nullptr); 101 } 102 } 103 104 NetDbRequests::NetDbRequests (): 105 RunnableServiceWithWork ("NetDbReq"), 106 m_ManageRequestsTimer (GetIOService ()), m_ExploratoryTimer (GetIOService ()), 107 m_CleanupTimer (GetIOService ()), m_DiscoveredRoutersTimer (GetIOService ()), 108 m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL) 109 { 110 } 111 112 NetDbRequests::~NetDbRequests () 113 { 114 Stop (); 115 } 116 117 void NetDbRequests::Start () 118 { 119 if (!IsRunning ()) 120 { 121 StartIOService (); 122 ScheduleManageRequests (); 123 ScheduleCleanup (); 124 if (!i2p::context.IsHidden ()) 125 ScheduleExploratory (EXPLORATORY_REQUEST_INTERVAL); 126 } 127 } 128 129 void NetDbRequests::Stop () 130 { 131 if (IsRunning ()) 132 { 133 m_ManageRequestsTimer.cancel (); 134 m_ExploratoryTimer.cancel (); 135 m_CleanupTimer.cancel (); 136 StopIOService (); 137 138 m_RequestedDestinations.clear (); 139 m_RequestedDestinationsPool.CleanUpMt (); 140 } 141 } 142 143 void NetDbRequests::ScheduleCleanup () 144 { 145 m_CleanupTimer.expires_from_now (boost::posix_time::seconds(REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL)); 146 m_CleanupTimer.async_wait (std::bind (&NetDbRequests::HandleCleanupTimer, 147 this, std::placeholders::_1)); 148 } 149 150 void NetDbRequests::HandleCleanupTimer (const boost::system::error_code& ecode) 151 { 152 if (ecode != boost::asio::error::operation_aborted) 153 { 154 m_RequestedDestinationsPool.CleanUpMt (); 155 ScheduleCleanup (); 156 } 157 } 158 159 std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, 160 bool isExploratory, bool direct, RequestedDestination::RequestComplete requestComplete) 161 { 162 // request RouterInfo directly 163 auto dest = m_RequestedDestinationsPool.AcquireSharedMt (destination, isExploratory, direct); 164 if (requestComplete) 165 dest->AddRequestComplete (requestComplete); 166 167 auto ret = m_RequestedDestinations.emplace (destination, dest); 168 if (!ret.second) // not inserted 169 { 170 dest->ResetRequestComplete (); // don't call requestComplete in destructor 171 dest = ret.first->second; // existing one 172 if (requestComplete) 173 { 174 if (dest->IsActive ()) 175 dest->AddRequestComplete (requestComplete); 176 else 177 requestComplete (nullptr); 178 } 179 return nullptr; 180 } 181 return dest; 182 } 183 184 void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r) 185 { 186 boost::asio::post (GetIOService (), [this, ident, r]() 187 { 188 std::shared_ptr<RequestedDestination> request; 189 auto it = m_RequestedDestinations.find (ident); 190 if (it != m_RequestedDestinations.end ()) 191 { 192 request = it->second; 193 if (request->IsExploratory ()) 194 m_RequestedDestinations.erase (it); 195 // otherwise cache for a while 196 } 197 if (request) 198 { 199 if (r) 200 request->Success (r); 201 else 202 request->Fail (); 203 } 204 }); 205 } 206 207 std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const 208 { 209 auto it = m_RequestedDestinations.find (ident); 210 if (it != m_RequestedDestinations.end ()) 211 return it->second; 212 return nullptr; 213 } 214 215 void NetDbRequests::ManageRequests () 216 { 217 uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); 218 for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) 219 { 220 auto& dest = it->second; 221 if (dest->IsActive () || ts < dest->GetCreationTime () + REQUEST_CACHE_TIME) 222 { 223 if (!dest->IsExploratory ()) 224 { 225 // regular request 226 bool done = false; 227 if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME) 228 { 229 if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME)) 230 // try next floodfill if no response after min interval 231 done = !SendNextRequest (dest); 232 } 233 else // request is expired 234 done = true; 235 if (done) 236 dest->Fail (); 237 it++; 238 } 239 else 240 { 241 // exploratory 242 if (ts >= dest->GetCreationTime () + MAX_EXPLORATORY_REQUEST_TIME) 243 { 244 dest->Fail (); 245 it = m_RequestedDestinations.erase (it); // delete expired exploratory request right a way 246 } 247 else 248 it++; 249 } 250 } 251 else 252 it = m_RequestedDestinations.erase (it); 253 } 254 } 255 256 bool NetDbRequests::SendNextRequest (std::shared_ptr<RequestedDestination> dest) 257 { 258 if (!dest || !dest->IsActive ()) return false; 259 bool ret = true; 260 auto count = dest->GetNumAttempts (); 261 if (!dest->IsExploratory () && count < MAX_NUM_REQUEST_ATTEMPTS) 262 { 263 auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); 264 if (nextFloodfill) 265 { 266 bool direct = dest->IsDirect (); 267 if (direct && !nextFloodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && 268 !i2p::transport::transports.IsConnected (nextFloodfill->GetIdentHash ())) 269 direct = false; // floodfill can't be reached directly 270 auto s = shared_from_this (); 271 auto onDrop = [s, dest]() 272 { 273 if (dest->IsActive ()) 274 { 275 boost::asio::post (s->GetIOService (), [s, dest]() 276 { 277 if (dest->IsActive ()) s->SendNextRequest (dest); 278 }); 279 } 280 }; 281 if (direct) 282 { 283 if (CheckLogLevel (eLogDebug)) 284 LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " directly"); 285 auto msg = dest->CreateRequestMessage (nextFloodfill->GetIdentHash ()); 286 msg->onDrop = onDrop; 287 i2p::transport::transports.SendMessage (nextFloodfill->GetIdentHash (), msg); 288 } 289 else 290 { 291 auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); 292 if (pool) 293 { 294 auto outbound = pool->GetNextOutboundTunnel (); 295 auto inbound = pool->GetNextInboundTunnel (); 296 if (nextFloodfill && outbound && inbound) 297 { 298 if (CheckLogLevel (eLogDebug)) 299 LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " through tunnels"); 300 auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); 301 msg->onDrop = onDrop; 302 outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0, 303 i2p::garlic::WrapECIESX25519MessageForRouter (msg, nextFloodfill->GetIdentity ()->GetEncryptionPublicKey ())); 304 } 305 else 306 { 307 ret = false; 308 if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels"); 309 if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels"); 310 } 311 } 312 else 313 { 314 ret = false; 315 LogPrint (eLogWarning, "NetDbReq: Exploratory pool is not ready"); 316 } 317 } 318 } 319 else 320 { 321 ret = false; 322 LogPrint (eLogWarning, "NetDbReq: No more floodfills for ", dest->GetDestination ().ToBase64 (), " after ", count, "attempts"); 323 } 324 } 325 else 326 { 327 if (!dest->IsExploratory ()) 328 LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after ", MAX_NUM_REQUEST_ATTEMPTS," attempts"); 329 ret = false; 330 } 331 return ret; 332 } 333 334 void NetDbRequests::ScheduleManageRequests () 335 { 336 m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL + 337 m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE)); 338 m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer, 339 this, std::placeholders::_1)); 340 } 341 342 void NetDbRequests::HandleManageRequestsTimer (const boost::system::error_code& ecode) 343 { 344 if (ecode != boost::asio::error::operation_aborted) 345 { 346 if (i2p::tunnel::tunnels.GetExploratoryPool ()) // expolratory pool is ready? 347 ManageRequests (); 348 ScheduleManageRequests (); 349 } 350 } 351 352 void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg) 353 { 354 boost::asio::post (GetIOService (), [this, msg]() 355 { 356 HandleDatabaseSearchReplyMsg (msg); 357 }); 358 } 359 360 void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg) 361 { 362 const uint8_t * buf = msg->GetPayload (); 363 std::string key; 364 size_t num = buf[32]; // num 365 if (CheckLogLevel (eLogInfo)) 366 key = i2p::data::ByteStreamToBase64 (buf, 32); 367 LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num); 368 369 IdentHash ident (buf); 370 bool isExploratory = false; 371 auto dest = FindRequest (ident); 372 if (dest && dest->IsActive ()) 373 { 374 isExploratory = dest->IsExploratory (); 375 if (!isExploratory && (num > 0 || dest->GetNumAttempts () < 3)) // before 3-rd attempt might be just bad luck 376 { 377 // try to send next requests 378 if (!SendNextRequest (dest)) 379 RequestComplete (ident, nullptr); 380 } 381 else 382 // no more requests for destination possible. delete it 383 RequestComplete (ident, nullptr); 384 } 385 else /*if (!m_FloodfillBootstrap)*/ 386 { 387 LogPrint (eLogInfo, "NetDbReq: Unsolicited or late database search reply for ", key); 388 return; 389 } 390 391 // try responses 392 if (num > NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES) 393 { 394 LogPrint (eLogWarning, "NetDbReq: Too many peer hashes ", num, " in database search reply, Reduced to ", NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES); 395 num = NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES; 396 } 397 if (isExploratory && !m_DiscoveredRouterHashes.empty ()) 398 { 399 // request outstanding routers 400 for (auto it: m_DiscoveredRouterHashes) 401 RequestRouter (it); 402 m_DiscoveredRouterHashes.clear (); 403 m_DiscoveredRoutersTimer.cancel (); 404 } 405 for (size_t i = 0; i < num; i++) 406 { 407 IdentHash router (buf + 33 + i*32); 408 if (CheckLogLevel (eLogDebug)) 409 LogPrint (eLogDebug, "NetDbReq: ", i, ": ", router.ToBase64 ()); 410 411 if (isExploratory) 412 // postpone request 413 m_DiscoveredRouterHashes.push_back (router); 414 else 415 // send request right a way 416 RequestRouter (router); 417 } 418 if (isExploratory && !m_DiscoveredRouterHashes.empty ()) 419 ScheduleDiscoveredRoutersRequest (); 420 } 421 422 void NetDbRequests::RequestRouter (const IdentHash& router) 423 { 424 auto r = netdb.FindRouter (router); 425 if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) 426 { 427 // router with ident not found or too old (1 hour) 428 LogPrint (eLogDebug, "NetDbReq: Found new/outdated router. Requesting RouterInfo..."); 429 if (!IsRouterBanned (router)) 430 RequestDestination (router, nullptr, true); 431 else 432 LogPrint (eLogDebug, "NetDbReq: Router ", router.ToBase64 (), " is banned. Skipped"); 433 } 434 else 435 LogPrint (eLogDebug, "NetDbReq: [:|||:]"); 436 } 437 438 void NetDbRequests::PostRequestDestination (const IdentHash& destination, 439 const RequestedDestination::RequestComplete& requestComplete, bool direct) 440 { 441 boost::asio::post (GetIOService (), [this, destination, requestComplete, direct]() 442 { 443 RequestDestination (destination, requestComplete, direct); 444 }); 445 } 446 447 void NetDbRequests::RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct) 448 { 449 auto dest = CreateRequest (destination, false, direct, requestComplete); // non-exploratory 450 if (dest) 451 { 452 if (!SendNextRequest (dest)) 453 RequestComplete (destination, nullptr); 454 } 455 else 456 LogPrint (eLogWarning, "NetDbReq: Destination ", destination.ToBase64(), " is requested already or cached"); 457 } 458 459 void NetDbRequests::Explore (int numDestinations) 460 { 461 // new requests 462 auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); 463 auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; 464 auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; 465 bool throughTunnels = outbound && inbound; 466 467 uint8_t randomHash[32]; 468 std::vector<i2p::tunnel::TunnelMessageBlock> msgs; 469 LogPrint (eLogInfo, "NetDbReq: Exploring new ", numDestinations, " routers ..."); 470 for (int i = 0; i < numDestinations; i++) 471 { 472 RAND_bytes (randomHash, 32); 473 auto dest = CreateRequest (randomHash, true, !throughTunnels); // exploratory 474 if (!dest) 475 { 476 LogPrint (eLogWarning, "NetDbReq: Exploratory destination is requested already"); 477 return; 478 } 479 auto floodfill = netdb.GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); 480 if (floodfill) 481 { 482 if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) 483 throughTunnels = false; 484 if (throughTunnels) 485 { 486 msgs.push_back (i2p::tunnel::TunnelMessageBlock 487 { 488 i2p::tunnel::eDeliveryTypeRouter, 489 floodfill->GetIdentHash (), 0, 490 CreateDatabaseStoreMsg () // tell floodfill about us 491 }); 492 msgs.push_back (i2p::tunnel::TunnelMessageBlock 493 { 494 i2p::tunnel::eDeliveryTypeRouter, 495 floodfill->GetIdentHash (), 0, 496 dest->CreateRequestMessage (floodfill, inbound) // explore 497 }); 498 } 499 else 500 i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); 501 } 502 else 503 RequestComplete (randomHash, nullptr); 504 } 505 if (throughTunnels && msgs.size () > 0) 506 outbound->SendTunnelDataMsgs (msgs); 507 } 508 509 void NetDbRequests::ScheduleExploratory (uint64_t interval) 510 { 511 m_ExploratoryTimer.expires_from_now (boost::posix_time::seconds(interval)); 512 m_ExploratoryTimer.async_wait (std::bind (&NetDbRequests::HandleExploratoryTimer, 513 this, std::placeholders::_1)); 514 } 515 516 void NetDbRequests::HandleExploratoryTimer (const boost::system::error_code& ecode) 517 { 518 if (ecode != boost::asio::error::operation_aborted) 519 { 520 auto numRouters = netdb.GetNumRouters (); 521 auto nextExploratoryInterval = numRouters < 2500 ? (EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL)/2 : 522 EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL_VARIANCE; 523 if (numRouters) 524 { 525 if (i2p::transport::transports.IsOnline () && i2p::transport::transports.IsRunning ()) 526 { 527 // explore only if online 528 numRouters = 800/numRouters; 529 if (numRouters < 1) numRouters = 1; 530 if (numRouters > 9) numRouters = 9; 531 Explore (numRouters); 532 } 533 } 534 else 535 LogPrint (eLogError, "NetDbReq: No known routers, reseed seems to be totally failed"); 536 ScheduleExploratory (nextExploratoryInterval); 537 } 538 } 539 540 void NetDbRequests::ScheduleDiscoveredRoutersRequest () 541 { 542 m_DiscoveredRoutersTimer.expires_from_now (boost::posix_time::milliseconds( 543 DISCOVERED_REQUEST_INTERVAL + m_Rng () % DISCOVERED_REQUEST_INTERVAL_VARIANCE)); 544 m_DiscoveredRoutersTimer.async_wait (std::bind (&NetDbRequests::HandleDiscoveredRoutersTimer, 545 this, std::placeholders::_1)); 546 } 547 548 void NetDbRequests::HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode) 549 { 550 if (ecode != boost::asio::error::operation_aborted) 551 { 552 if (!m_DiscoveredRouterHashes.empty ()) 553 { 554 RequestRouter (m_DiscoveredRouterHashes.front ()); 555 m_DiscoveredRouterHashes.pop_front (); 556 if (!m_DiscoveredRouterHashes.empty ()) // more hashes to request 557 ScheduleDiscoveredRoutersRequest (); 558 } 559 } 560 } 561 } 562 }