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 }