/ libi2pd / NetDbRequests.cpp
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  }