void HandleI2NPMessage (uint8_t * msg, size_t len) { uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); uint8_t * buf = msg + I2NP_HEADER_SIZE; int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); switch (typeID) { case eI2NPVariableTunnelBuild: HandleVariableTunnelBuildMsg (msgID, buf, size); break; case eI2NPVariableTunnelBuildReply: HandleVariableTunnelBuildReplyMsg (msgID, buf, size); break; case eI2NPTunnelBuild: HandleTunnelBuildMsg (buf, size); break; case eI2NPTunnelBuildReply: // TODO: break; default: LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID); } }
void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg) { const uint8_t * buf = msg->GetPayload (); uint32_t msgID = bufbe32toh (buf); buf += 4; uint64_t timestamp = bufbe64toh (buf); auto it = m_Tests.find (msgID); if (it != m_Tests.end ()) { // restore from test failed state if any if (it->second.first->GetState () == eTunnelStateTestFailed) it->second.first->SetState (eTunnelStateEstablished); if (it->second.second->GetState () == eTunnelStateTestFailed) it->second.second->SetState (eTunnelStateEstablished); LogPrint (eLogDebug, "Tunnels: test of ", it->first, " successful. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); m_Tests.erase (it); } else { if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } }
void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) { LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); return; } auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); if (tunnel) { // endpoint of inbound tunnel LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); if (tunnel->HandleTunnelBuildResponse (buf, len)) { LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); i2p::tunnel::tunnels.AddInboundTunnel (tunnel); } else { LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); } } else { uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPVariableTunnelBuildReply, buf, len, bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } } }
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) { uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) { LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply if (i2p::context.AcceptsTunnels () && i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS && !i2p::transport::transports.IsBandwidthExceeded ()) { i2p::tunnel::TransitTunnel * transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0; } else record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30) //TODO: fill filler SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret record + BUILD_RESPONSE_RECORD_HASH_OFFSET); // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) { encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); } return true; } } return false; }
void I2CPSession::HandleNextMessage (const uint8_t * buf) { auto handler = m_Owner.GetMessagesHandlers ()[buf[I2CP_HEADER_TYPE_OFFSET]]; if (handler) (this->*handler)(buf + I2CP_HEADER_SIZE, bufbe32toh (buf + I2CP_HEADER_LENGTH_OFFSET)); else LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)buf[I2CP_HEADER_TYPE_OFFSET]); }
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) { uint32_t msgID = bufbe32toh (msg->GetPayload ()); { auto it = m_DeliveryStatusSessions.find (msgID); if (it != m_DeliveryStatusSessions.end ()) { it->second->MessageConfirmed (msgID); m_DeliveryStatusSessions.erase (it); LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); } } }
void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE) { LogPrint (eLogError, "TunnelBuild message is too short ", len); return; } uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) { if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPTunnelBuildReply, buf, len, bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPTunnelBuild, buf, len, bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } }
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg) { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ()); return; } buf += 4; // length auto it = m_Tags.find (SessionTag(buf)); if (it != m_Tags.end ()) { // tag found. Use AES auto decryption = it->second; m_Tags.erase (it); // tag might be used only once if (length >= 32) { uint8_t iv[32]; // IV is first 16 bytes SHA256(buf, 32, iv); decryption->SetIV (iv); decryption->Decrypt (buf + 32, length - 32, buf + 32); HandleAESBlock (buf + 32, length - 32, decryption, msg->from); } else LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); } else { // tag not found. Use ElGamal ElGamalBlock elGamal; if (length >= 514 && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx)) { auto decryption = std::make_shared<AESDecryption>(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); decryption->SetIV (iv); decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); } }
void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) { uint32_t msgID = bufbe32toh (msg->GetPayload ()); GarlicRoutingSessionPtr session; { std::unique_lock<std::mutex> l(m_DeliveryStatusSessionsMutex); auto it = m_DeliveryStatusSessions.find (msgID); if (it != m_DeliveryStatusSessions.end ()) { session = it->second; m_DeliveryStatusSessions.erase (it); } } if (session) { session->MessageConfirmed (msgID); LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); } }
void LeaseSet::ReadFromBuffer (bool readIdentity) { if (readIdentity || !m_Identity) m_Identity = std::make_shared<IdentityEx>(m_Buffer, m_BufferLen); size_t size = m_Identity->GetFullLen (); memcpy (m_EncryptionKey, m_Buffer + size, 256); size += 256; // encryption key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key uint8_t num = m_Buffer[size]; size++; // num LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); if (!num) m_IsValid = false; // process leases const uint8_t * leases = m_Buffer + size; for (int i = 0; i < num; i++) { Lease lease; lease.tunnelGateway = leases; leases += 32; // gateway lease.tunnelID = bufbe32toh (leases); leases += 4; // tunnel ID lease.endDate = bufbe64toh (leases); leases += 8; // end date m_Leases.push_back (lease); // check if lease's gateway is in our netDb if (!netdb.FindRouter (lease.tunnelGateway)) { // if not found request it LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); netdb.RequestDestination (lease.tunnelGateway); } } // verify if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { LogPrint (eLogWarning, "LeaseSet: verification failed"); m_IsValid = false; } }
void I2CPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { size_t offset = 0; if (m_NextMessage) { if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen) { memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); m_NextMessageOffset += bytes_transferred; } else { offset = m_NextMessageLen - m_NextMessageOffset; memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); HandleNextMessage (m_NextMessage); delete[] m_NextMessage; } } while (offset < bytes_transferred) { auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; if (msgLen <= bytes_transferred - offset) { HandleNextMessage (m_Buffer + offset); offset += msgLen; } else { m_NextMessageLen = msgLen; m_NextMessageOffset = bytes_transferred - offset; m_NextMessage = new uint8_t[m_NextMessageLen]; memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); offset = bytes_transferred; } } Receive (); } }
void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption, std::shared_ptr<i2p::tunnel::InboundTunnel> from) { uint16_t tagCount = bufbe16toh (buf); buf += 2; len -= 2; if (tagCount > 0) { if (tagCount*32 > len) { LogPrint (eLogError, "Garlic: Tag count ", tagCount, " exceeds length ", len); return ; } uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (int i = 0; i < tagCount; i++) m_Tags[SessionTag(buf + i*32, ts)] = decryption; } buf += tagCount*32; len -= tagCount*32; uint32_t payloadSize = bufbe32toh (buf); if (payloadSize > len) { LogPrint (eLogError, "Garlic: Unexpected payload size ", payloadSize); return; } buf += 4; uint8_t * payloadHash = buf; buf += 32;// payload hash. if (*buf) // session key? buf += 32; // new session key buf++; // flag // payload uint8_t digest[32]; SHA256 (buf, payloadSize, digest); if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match { LogPrint (eLogError, "Garlic: wrong payload hash"); return; } HandleGarlicPayload (buf, payloadSize, from); }
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) { if (len < 1) { LogPrint (eLogError, "Garlic: payload is too short"); return; } int numCloves = buf[0]; LogPrint (eLogDebug, "Garlic: ", numCloves," cloves"); buf++; len--; for (int i = 0; i < numCloves; i++) { const uint8_t * buf1 = buf; // delivery instructions uint8_t flag = buf[0]; buf++; // flag if (flag & 0x80) // encrypted? { // TODO: implement LogPrint (eLogWarning, "Garlic: clove encrypted"); buf += 32; } ptrdiff_t offset = buf - buf1; GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (deliveryType) { case eGarlicDeliveryTypeLocal: LogPrint (eLogDebug, "Garlic: type local"); if (offset > (int)len) { LogPrint (eLogError, "Garlic: message is too short"); break; } HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeDestination: LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // destination. check it later or for multiple destinations offset = buf - buf1; if (offset > (int)len) { LogPrint (eLogError, "Garlic: message is too short"); break; } HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeTunnel: { LogPrint (eLogDebug, "Garlic: type tunnel"); // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; offset = buf - buf1; if (offset + 4 > (int)len) { LogPrint (eLogError, "Garlic: message is too short"); break; } uint32_t gwTunnel = bufbe32toh (buf); buf += 4; offset += 4; auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset), from); if (from) // received through an inbound tunnel { std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel; if (from->GetTunnelPool ()) tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); else LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); if (tunnel) // we have send it through an outbound tunnel tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } else // received directly i2p::transport::transports.SendMessage (gwHash, i2p::CreateTunnelGatewayMsg (gwTunnel, msg)); // send directly break; } case eGarlicDeliveryTypeRouter: { uint8_t * ident = buf; buf += 32; offset = buf - buf1; if (!from) // received directly { if (offset > (int)len) { LogPrint (eLogError, "Garlic: message is too short"); break; } i2p::transport::transports.SendMessage (ident, CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset))); } else LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported"); break; } default: LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } if (offset > (int)len) { LogPrint (eLogError, "Garlic: message is too short"); break; } buf += GetI2NPMessageLength (buf, len - offset); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate offset = buf - buf1; if (offset > (int)len) { LogPrint (eLogError, "Garlic: clove is too long"); break; } len -= offset; } }
void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from) { const uint8_t * buf1 = buf; int numCloves = buf[0]; LogPrint (eLogDebug, "Garlic: ", numCloves," cloves"); buf++; for (int i = 0; i < numCloves; i++) { // delivery instructions uint8_t flag = buf[0]; buf++; // flag if (flag & 0x80) // encrypted? { // TODO: implement LogPrint (eLogWarning, "Garlic: clove encrypted"); buf += 32; } GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (deliveryType) { case eGarlicDeliveryTypeLocal: LogPrint (eLogDebug, "Garlic: type local"); HandleI2NPMessage (buf, len, from); break; case eGarlicDeliveryTypeDestination: LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // destination. check it later or for multiple destinations HandleI2NPMessage (buf, len, from); break; case eGarlicDeliveryTypeTunnel: { LogPrint (eLogDebug, "Garlic: type tunnel"); // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; uint32_t gwTunnel = bufbe32toh (buf); buf += 4; std::shared_ptr<i2p::tunnel::OutboundTunnel> tunnel; if (from && from->GetTunnelPool ()) tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) // we have send it through an outbound tunnel { auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); } else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); break; } case eGarlicDeliveryTypeRouter: LogPrint (eLogWarning, "Garlic: type router not supported"); buf += 32; break; default: LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } buf += GetI2NPMessageLength (buf); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate if (buf - buf1 > (int)len) { LogPrint (eLogError, "Garlic: clove is too long"); break; } } }
void GarlicDestination::HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg) { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { LogPrint (eLogError, "Garlic message length ", length, " exceeds I2NP message length ", msg->GetLength ()); return; } buf += 4; // length auto it = m_Tags.find (SessionTag(buf)); if (it != m_Tags.end ()) { // tag found. Use AES if (length >= 32) { uint8_t iv[32]; // IV is first 16 bytes CryptoPP::SHA256().CalculateDigest(iv, buf, 32); it->second->SetIV (iv); it->second->Decrypt (buf + 32, length - 32, buf + 32); HandleAESBlock (buf + 32, length - 32, it->second, msg->from); } else LogPrint (eLogError, "Garlic message length ", length, " is less than 32 bytes"); m_Tags.erase (it); // tag might be used only once } else { // tag not found. Use ElGamal ElGamalBlock elGamal; if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true)) { auto decryption = std::make_shared<i2p::crypto::CBCDecryption>(); decryption->SetKey (elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32); decryption->SetIV (iv); decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else LogPrint (eLogError, "Failed to decrypt garlic"); } // cleanup expired tags uint32_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts > m_LastTagsCleanupTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) { if (m_LastTagsCleanupTime) { int numExpiredTags = 0; for (auto it = m_Tags.begin (); it != m_Tags.end ();) { if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) { numExpiredTags++; it = m_Tags.erase (it); } else it++; } LogPrint (numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); } m_LastTagsCleanupTime = ts; } }