/ libi2pd / SSU2OutOfSession.cpp
SSU2OutOfSession.cpp
  1  /*
  2  * Copyright (c) 2024-2026, 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 "SSU2.h"
 11  #include "SSU2OutOfSession.h"
 12  
 13  namespace i2p
 14  {
 15  namespace transport
 16  {
 17  	SSU2PeerTestSession::SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID):
 18  		SSU2Session (server, nullptr, nullptr, false),
 19  		m_MsgNumReceived (0), m_NumResends (0),m_IsConnectedRecently (false), m_IsStatusChanged (false),
 20  		m_PeerTestResendTimer (server.GetService ())
 21  	{
 22  		if (!sourceConnID) sourceConnID = ~destConnID;
 23  		if (!destConnID) destConnID = ~sourceConnID;
 24  		SetSourceConnID (sourceConnID);
 25  		SetDestConnID (destConnID);
 26  		SetState (eSSU2SessionStatePeerTest);
 27  		SetTerminationTimeout (SSU2_PEER_TEST_EXPIRATION_TIMEOUT);
 28  	}
 29  
 30  	bool SSU2PeerTestSession::ProcessPeerTest (uint8_t * buf, size_t len)
 31  	{
 32  		// we are Alice or Charlie, msgs 5,6,7
 33  		Header header;
 34  		memcpy (header.buf, buf, 16);
 35  		header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24));
 36  		header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12));
 37  		if (header.h.type != eSSU2PeerTest)
 38  		{
 39  			LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest);
 40  			return false;
 41  		}
 42  		if (len < 48)
 43  		{
 44  			LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
 45  			return false;
 46  		}
 47  		uint8_t nonce[12] = {0};
 48  		uint64_t headerX[2]; // sourceConnID, token
 49  		GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
 50  		SetDestConnID (headerX[0]);
 51  		// decrypt and handle payload
 52  		uint8_t * payload = buf + 32;
 53  		CreateNonce (be32toh (header.h.packetNum), nonce);
 54  		uint8_t h[32];
 55  		memcpy (h, header.buf, 16);
 56  		memcpy (h + 16, &headerX, 16);
 57  		if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32,
 58  			i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false))
 59  		{
 60  			LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed ");
 61  			return false;
 62  		}
 63  		HandlePayload (payload, len - 48);
 64  		SetIsDataReceived (false);
 65  		return true;
 66  	}
 67  
 68  	void SSU2PeerTestSession::HandleAddress (const uint8_t * buf, size_t len)
 69  	{
 70  		if (!ExtractEndpoint (buf, len, m_OurEndpoint))
 71  			LogPrint (eLogWarning, "SSU2: Can't handle address block from peer test message");
 72  	}
 73  
 74  	void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len)
 75  	{
 76  		// msgs 5-7
 77  		if (len < 8) return;
 78  		uint8_t msg = buf[0];
 79  		if (msg <= m_MsgNumReceived)
 80  		{
 81  			LogPrint (eLogDebug, "SSU2: PeerTest msg num ", msg, " received after ", m_MsgNumReceived, ". Ignored");
 82  			return;
 83  		}
 84  		size_t offset = 3; // points to signed data after msg + code + flag
 85  		uint32_t nonce = bufbe32toh (buf + offset + 1); // 1 - ver
 86  		switch (msg) // msg
 87  		{
 88  			case 5: // Alice from Charlie 1
 89  			{
 90  				if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ())
 91  				{
 92  					m_PeerTestResendTimer.cancel (); // cancel delayed msg 6 if any
 93  					if (GetServer ().IsForcedFirewalled (GetRemoteEndpoint ().address().is_v4()))
 94  						// we assume that msg 5 was not received if forced firewalled
 95  						return;
 96  					m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ());
 97  					if (GetAddress ())
 98  					{
 99  						if (!m_IsConnectedRecently)
100  							SetRouterStatus (eRouterStatusOK);
101  						else if (m_IsStatusChanged && GetRouterStatus () == eRouterStatusFirewalled)
102  							SetRouterStatus (eRouterStatusUnknown);
103  						SendPeerTest (6, buf + offset, len - offset);
104  					}
105  				}
106  				else
107  					LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", GetSourceConnID ());
108  				break;
109  			}
110  			case 6: // Charlie from Alice
111  			{
112  				m_PeerTestResendTimer.cancel (); // no more msg 5 resends
113  				if (GetAddress ())
114  					SendPeerTest (7, buf + offset, len - offset);
115  				else
116  					LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6");
117  				GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ());
118  				GetServer ().RequestRemoveSession (GetConnID ());
119  				break;
120  			}
121  			case 7: // Alice from Charlie 2
122  			{
123  				m_PeerTestResendTimer.cancel (); // no more msg 6 resends
124  				if (m_MsgNumReceived < 5 && m_OurEndpoint.port ()) // msg 5 was not received
125  				{
126  					if (m_OurEndpoint.address ().is_v4 ()) // ipv4
127  					{
128  						if (i2p::context.GetStatus () == eRouterStatusFirewalled)
129  						{
130  						    if (m_OurEndpoint.port () != GetServer ().GetPort (true))
131  								i2p::context.SetError (eRouterErrorSymmetricNAT);
132  							else if (i2p::context.GetError () == eRouterErrorSymmetricNAT)
133  								i2p::context.SetError (eRouterErrorNone);
134  						}
135  					}
136  					else
137  					{
138  						if (i2p::context.GetStatusV6 () == eRouterStatusFirewalled)
139  						{
140  						    if (m_OurEndpoint.port () != GetServer ().GetPort (false))
141  								i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT);
142  							else if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT)
143  								i2p::context.SetErrorV6 (eRouterErrorNone);
144  						}
145  					}
146  				}
147  				GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ());
148  				GetServer ().RequestRemoveSession (GetConnID ());
149  				break;
150  			}
151  			default:
152  				LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", msg);
153  				return;
154  		}
155  		m_MsgNumReceived = msg;
156  	}
157  
158  	void SSU2PeerTestSession::SendPeerTest (uint8_t msg)
159  	{
160  		auto addr = GetAddress ();
161  		if (!addr) return;
162  		Header header;
163  		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
164  		// fill packet
165  		header.h.connID = GetDestConnID (); // dest id
166  		RAND_bytes (header.buf + 8, 4); // random packet num
167  		header.h.type = eSSU2PeerTest;
168  		header.h.flags[0] = 2; // ver
169  		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
170  		header.h.flags[2] = 0; // flag
171  		memcpy (h, header.buf, 16);
172  		htobuf64 (h + 16, GetSourceConnID ()); // source id
173  		// payload
174  		payload[0] = eSSU2BlkDateTime;
175  		htobe16buf (payload + 1, 4);
176  		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
177  		size_t payloadSize = 7;
178  		if (msg == 6 || msg == 7)
179  			payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, GetRemoteEndpoint ());
180  		payloadSize += CreatePeerTestBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize,
181  			msg, eSSU2PeerTestCodeAccept, nullptr, m_SignedData.data (), m_SignedData.size ());
182  		payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
183  		// encrypt
184  		uint8_t n[12];
185  		CreateNonce (be32toh (header.h.packetNum), n);
186  		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
187  		payloadSize += 16;
188  		header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
189  		header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
190  		memset (n, 0, 12);
191  		GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
192  		// send
193  		GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ());
194  		UpdateNumSentBytes (payloadSize + 32);
195  	}
196  
197  	void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed)
198  	{
199  		m_SignedData.assign (signedData, signedData + signedDataLen);
200  		if (!delayed)
201  			SendPeerTest (msg);
202  		// schedule resend for msgs 5 or 6
203  		if (msg == 5 || msg == 6)
204  			ScheduleResend (msg);
205  	}
206  
207  	void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen,
208  		std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool delayed)
209  	{
210  		if (!addr) return;
211  		SetAddress (addr);
212  		SendPeerTest (msg, signedData, signedDataLen, delayed);
213  	}
214  
215  	void SSU2PeerTestSession::Connect ()
216  	{
217  		LogPrint (eLogError, "SSU2: Can't connect peer test session");
218  	}
219  
220  	bool SSU2PeerTestSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
221  	{
222  		LogPrint (eLogError, "SSU2: Can't handle incoming message in peer test session");
223  		return false;
224  	}
225  
226  	void SSU2PeerTestSession::ScheduleResend (uint8_t msg)
227  	{
228  		if (m_NumResends < SSU2_PEER_TEST_MAX_NUM_RESENDS)
229  		{
230  			m_PeerTestResendTimer.expires_from_now (boost::posix_time::milliseconds(
231  				SSU2_PEER_TEST_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE));
232  			std::weak_ptr<SSU2PeerTestSession> s(std::static_pointer_cast<SSU2PeerTestSession>(shared_from_this ()));
233  			m_PeerTestResendTimer.async_wait ([s, msg](const boost::system::error_code& ecode)
234  				{
235  					if (ecode != boost::asio::error::operation_aborted)
236  					{
237  						auto s1 = s.lock ();
238  						if (s1)
239  						{
240  							if (msg > s1->m_MsgNumReceived)
241  							{
242  								s1->SendPeerTest (msg);
243  								s1->m_NumResends++;
244  								s1->ScheduleResend (msg);
245  							}
246  						}
247  					}
248  				});
249  		}
250  	}
251  
252  	SSU2HolePunchSession::SSU2HolePunchSession (SSU2Server& server, uint32_t nonce,
253  		const boost::asio::ip::udp::endpoint& remoteEndpoint,
254  		std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
255  		SSU2Session (server), // we create full incoming session
256  		m_NumResends (0), m_HolePunchResendTimer (server.GetService ())
257  	{
258  		// we are Charlie
259  		uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
260  		uint64_t sourceConnID = ~destConnID;
261  		SetSourceConnID (sourceConnID);
262  		SetDestConnID (destConnID);
263  		SetState (eSSU2SessionStateHolePunch);
264  		SetRemoteEndpoint (remoteEndpoint);
265  		SetAddress (addr);
266  		SetTerminationTimeout (SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT);
267  	}
268  
269  	void SSU2HolePunchSession::SendHolePunch ()
270  	{
271  		auto addr = GetAddress ();
272  		if (!addr) return;
273  		auto& ep = GetRemoteEndpoint ();
274  		LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep);
275  		Header header;
276  		uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
277  		// fill packet
278  		header.h.connID = GetDestConnID (); // dest id
279  		RAND_bytes (header.buf + 8, 4); // random packet num
280  		header.h.type = eSSU2HolePunch;
281  		header.h.flags[0] = 2; // ver
282  		header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
283  		header.h.flags[2] = 0; // flag
284  		memcpy (h, header.buf, 16);
285  		htobuf64 (h + 16, GetSourceConnID ()); // source id
286  		RAND_bytes (h + 24, 8); // header token, to be ignored by Alice
287  		// payload
288  		payload[0] = eSSU2BlkDateTime;
289  		htobe16buf (payload + 1, 4);
290  		htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
291  		size_t payloadSize = 7;
292  		payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, ep);
293  		// relay response block
294  		if (payloadSize + m_RelayResponseBlock.size () < GetMaxPayloadSize ())
295  		{
296  			memcpy (payload + payloadSize, m_RelayResponseBlock.data (), m_RelayResponseBlock.size ());
297  			payloadSize += m_RelayResponseBlock.size ();
298  		}
299  		payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
300  		// encrypt
301  		uint8_t n[12];
302  		CreateNonce (be32toh (header.h.packetNum), n);
303  		i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
304  		payloadSize += 16;
305  		header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
306  		header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
307  		memset (n, 0, 12);
308  		GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
309  		// send
310  		GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
311  		UpdateNumSentBytes (payloadSize + 32);
312  	}
313  
314  	void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen)
315  	{
316  		m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen);
317  		SendHolePunch ();
318  		ScheduleResend ();
319  	}
320  
321  	void SSU2HolePunchSession::ScheduleResend ()
322  	{
323  		if (m_NumResends < SSU2_HOLE_PUNCH_MAX_NUM_RESENDS)
324  		{
325  			m_HolePunchResendTimer.expires_from_now (boost::posix_time::milliseconds(
326  				SSU2_HOLE_PUNCH_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE));
327  			std::weak_ptr<SSU2HolePunchSession> s(std::static_pointer_cast<SSU2HolePunchSession>(shared_from_this ()));
328  			m_HolePunchResendTimer.async_wait ([s](const boost::system::error_code& ecode)
329  				{
330  					if (ecode != boost::asio::error::operation_aborted)
331  					{
332  						auto s1 = s.lock ();
333  						if (s1 && s1->GetState () == eSSU2SessionStateHolePunch)
334  						{
335  							s1->SendHolePunch ();
336  							s1->m_NumResends++;
337  							s1->ScheduleResend ();
338  						}
339  					}
340  				});
341  		}
342  	}
343  
344  	bool SSU2HolePunchSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
345  	{
346  		m_HolePunchResendTimer.cancel ();
347  		return SSU2Session::ProcessFirstIncomingMessage (connID, buf, len);
348  	}
349  }
350  }