/ libi2pd / TunnelEndpoint.cpp
TunnelEndpoint.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 "I2PEndian.h"
 10  #include <string.h>
 11  #include "Crypto.h"
 12  #include "Log.h"
 13  #include "NetDb.hpp"
 14  #include "I2NPProtocol.h"
 15  #include "Transports.h"
 16  #include "RouterContext.h"
 17  #include "Timestamp.h"
 18  #include "TunnelEndpoint.h"
 19  
 20  namespace i2p
 21  {
 22  namespace tunnel
 23  {
 24  	
 25  	void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr<I2NPMessage> msg)
 26  	{
 27  		m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE;
 28  
 29  		uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16
 30  		uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // without 4-byte checksum
 31  		if (zero)
 32  		{
 33  			uint8_t * fragment = zero + 1;
 34  			// verify checksum
 35  			memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end
 36  			uint8_t hash[32];
 37  			SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv
 38  			if (memcmp (hash, decrypted, 4))
 39  			{
 40  				LogPrint (eLogError, "TunnelMessage: Checksum verification failed");
 41  				return;
 42  			}
 43  			// process fragments
 44  			while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
 45  			{
 46  				uint8_t flag = fragment[0];
 47  				fragment++;
 48  
 49  				bool isFollowOnFragment = flag & 0x80, isLastFragment = true;
 50  				uint32_t msgID = 0;
 51  				int fragmentNum = 0;
 52  				if (!isFollowOnFragment)
 53  				{
 54  					// first fragment
 55  					if (m_CurrentMsgID)
 56  						AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete
 57  
 58  					m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03);
 59  					switch (m_CurrentMessage.deliveryType)
 60  					{
 61  						case eDeliveryTypeLocal: // 0
 62  						break;
 63  						case eDeliveryTypeTunnel: // 1
 64  							m_CurrentMessage.tunnelID = bufbe32toh (fragment);
 65  							fragment += 4; // tunnelID
 66  							m_CurrentMessage.hash = i2p::data::IdentHash (fragment);
 67  							fragment += 32; // hash
 68  						break;
 69  						case eDeliveryTypeRouter: // 2
 70  							m_CurrentMessage.hash = i2p::data::IdentHash (fragment);
 71  							fragment += 32; // to hash
 72  						break;
 73  						default: ;
 74  					}
 75  
 76  					bool isFragmented = flag & 0x08;
 77  					if (isFragmented)
 78  					{
 79  						// Message ID
 80  						msgID = bufbe32toh (fragment);
 81  						fragment += 4;
 82  						m_CurrentMsgID = msgID;
 83  						isLastFragment = false;
 84  					}
 85  				}
 86  				else
 87  				{
 88  					// follow on
 89  					msgID = bufbe32toh (fragment); // MessageID
 90  					fragment += 4;
 91  					fragmentNum = (flag >> 1) & 0x3F; // 6 bits
 92  					isLastFragment = flag & 0x01;
 93  				}
 94  
 95  				uint16_t size = bufbe16toh (fragment);
 96  				fragment += 2;
 97  
 98  				// handle fragment
 99  				if (isFollowOnFragment)
100  				{
101  					// existing message
102  					if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum)
103  						HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous
104  					else
105  					{
106  						HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another
107  						m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
108  					}
109  				}
110  				else
111  				{
112  					// new message
113  					msg->offset = fragment - msg->buf;
114  					msg->len = msg->offset + size;
115  					// check message size
116  					if (msg->len > msg->maxLen)
117  					{
118  						LogPrint (eLogError, "TunnelMessage: Fragment is too long ", (int)size);
119  						m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
120  						return;
121  					}
122  					// create new or assign I2NP message
123  					if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE)
124  					{
125  						// this is not last message. we have to copy it
126  						m_CurrentMessage.data = NewI2NPTunnelMessage (true);
127  						*(m_CurrentMessage.data) = *msg;
128  					}
129  					else
130  						m_CurrentMessage.data = msg;
131  
132  					if (isLastFragment)
133  					{
134  						// single message
135  						HandleNextMessage (m_CurrentMessage);
136  						m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
137  					}
138  					else if (msgID)
139  					{
140  						// first fragment of a new message
141  						m_CurrentMessage.nextFragmentNum = 1;
142  						m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch ();
143  						HandleOutOfSequenceFragments (msgID, m_CurrentMessage);
144  					}
145  					else
146  					{
147  						LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented");
148  						m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
149  					}
150  				}
151  
152  				fragment += size;
153  			}
154  		}
155  		else
156  			LogPrint (eLogError, "TunnelMessage: Zero not found");
157  	}
158  
159  	void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment,
160  		uint8_t fragmentNum, const uint8_t * fragment, size_t size)
161  	{
162  		auto it = m_IncompleteMessages.find (msgID);
163  		if (it != m_IncompleteMessages.end())
164  		{
165  			auto& msg = it->second;
166  			if (fragmentNum == msg.nextFragmentNum)
167  			{
168  				if (ConcatFollowOnFragment (msg, fragment, size))
169  				{
170  					if (isLastFragment)
171  					{
172  						// message complete
173  						HandleNextMessage (msg);
174  						m_IncompleteMessages.erase (it);
175  					}
176  					else
177  					{
178  						msg.nextFragmentNum++;
179  						HandleOutOfSequenceFragments (msgID, msg);
180  					}
181  				}
182  				else
183  				{
184  					LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped");
185  					m_IncompleteMessages.erase (it);
186  				}
187  			}
188  			else
189  			{
190  				LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved");
191  				AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size);
192  			}
193  		}
194  		else
195  		{
196  			LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved");
197  			AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size);
198  		}
199  	}
200  
201  	bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const
202  	{
203  		if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long
204  		{
205  			if (msg.data->len + size > msg.data->maxLen)
206  			{
207  			//	LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough");
208  				auto newMsg = NewI2NPMessage (msg.data->len + size);
209  				*newMsg = *(msg.data);
210  				msg.data = newMsg;
211  			}
212  			if (msg.data->Concat (fragment, size) < size) // concatenate fragment
213  			{
214  				LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen);
215  				return false;
216  			}
217  		}
218  		else
219  			return false;
220  		return true;
221  	}
222  
223  	void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment)
224  	{
225  		if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size))
226  		{
227  			if (isLastFragment)
228  			{
229  				// message complete
230  				HandleNextMessage (m_CurrentMessage);
231  				m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
232  			}
233  			else
234  			{
235  				m_CurrentMessage.nextFragmentNum++;
236  				HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage);
237  			}
238  		}
239  		else
240  		{
241  			LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped");
242  			m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr;
243  		}
244  	}
245  
246  	void TunnelEndpoint::AddIncompleteCurrentMessage ()
247  	{
248  		if (m_CurrentMsgID)
249  		{
250  			auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage);
251  			if (!ret.second)
252  				LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists");
253  			m_CurrentMessage.data = nullptr;
254  			m_CurrentMsgID = 0;
255  		}
256  	}
257  
258  	void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum,
259  		bool isLastFragment, const uint8_t * fragment, size_t size)
260  	{
261  		if (!m_OutOfSequenceFragments.try_emplace ((uint64_t)msgID << 32 | fragmentNum, 
262  			isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), fragment, size).second)
263  			LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID);
264  	}
265  
266  	void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg)
267  	{
268  		while (ConcatNextOutOfSequenceFragment (msgID, msg))
269  		{
270  			if (!msg.nextFragmentNum) // message complete
271  			{
272  				HandleNextMessage (msg);
273  				if (&msg == &m_CurrentMessage)
274  				{
275  					m_CurrentMsgID = 0;
276  					m_CurrentMessage.data = nullptr;
277  				}
278  				else
279  					m_IncompleteMessages.erase (msgID);
280  				LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found");
281  				break;
282  			}
283  		}
284  	}
285  
286  	bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg)
287  	{
288  		auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum);
289  		if (it != m_OutOfSequenceFragments.end ())
290  		{
291  			LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found");
292  			size_t size = it->second.data.size ();
293  			if (msg.data->len + size > msg.data->maxLen)
294  			{
295  				LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough");
296  				auto newMsg = NewI2NPMessage (msg.data->len + size);
297  				*newMsg = *(msg.data);
298  				msg.data = newMsg;
299  			}
300  			if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment	
301  				LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen);
302  			if (it->second.isLastFragment)
303  				// message complete
304  				msg.nextFragmentNum = 0;
305  			else
306  				msg.nextFragmentNum++;
307  			m_OutOfSequenceFragments.erase (it);
308  			return true;
309  		}
310  		return false;
311  	}
312  
313  	void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg)
314  	{
315  		if (!m_IsInbound && msg.data->IsExpired ())
316  		{
317  			LogPrint (eLogInfo, "TunnelMessage: Message expired");
318  			return;
319  		}
320  		uint8_t typeID = msg.data->GetTypeID ();
321  		LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID);
322  		
323  		switch (msg.deliveryType)
324  		{
325  			case eDeliveryTypeLocal:
326  				i2p::HandleI2NPMessage (msg.data);
327  			break;
328  			case eDeliveryTypeTunnel:
329  				if (!m_IsInbound) // outbound transit tunnel
330  					SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
331  				else
332  					LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped");
333  			break;
334  			case eDeliveryTypeRouter:
335  				if (!m_IsInbound) // outbound transit tunnel
336  					i2p::transport::transports.SendMessage (msg.hash, msg.data); // send right away, because most likely it's single message
337  				else // we shouldn't send this message. possible leakage
338  					LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped");
339  			break;
340  			default:
341  				LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType);
342  		};
343  	}
344  
345  	void TunnelEndpoint::Cleanup ()
346  	{
347  		auto ts = i2p::util::GetMillisecondsSinceEpoch ();
348  		// out-of-sequence fragments
349  		for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();)
350  		{
351  			if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
352  				it = m_OutOfSequenceFragments.erase (it);
353  			else
354  				++it;
355  		}
356  		// incomplete messages
357  		for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
358  		{
359  			if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT)
360  				it = m_IncompleteMessages.erase (it);
361  			else
362  				++it;
363  		}
364  	}
365  
366  	void TunnelEndpoint::SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr<i2p::I2NPMessage> msg)
367  	{	
368  		if (msg)
369  		{	
370  			if (!m_Sender && m_I2NPMsgs.empty ()) // first message
371  				m_CurrentHash = to;
372  			else if (m_CurrentHash != to) // new target router
373  			{
374  				FlushI2NPMsgs (); //  flush message to previous
375  				if (m_Sender) m_Sender->Reset (); // reset sender
376  				m_CurrentHash = to; // set new target router
377  			}	// otherwise add msg to the list for current target router
378  			m_I2NPMsgs.push_back (msg);
379  		}	
380  	}
381  	
382  	void TunnelEndpoint::FlushI2NPMsgs ()
383  	{
384  		if (!m_I2NPMsgs.empty ())
385  		{
386  			if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
387  			m_Sender->SendMessagesTo (m_CurrentHash, m_I2NPMsgs); // send and clear
388  		}	
389  	}	
390  
391  	const i2p::data::IdentHash * TunnelEndpoint::GetCurrentHash () const
392  	{
393  		return (m_Sender || !m_I2NPMsgs.empty ()) ? &m_CurrentHash : nullptr;
394  	}	
395  }
396  }