diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp new file mode 100644 index 00000000..1fa7a982 --- /dev/null +++ b/I2NPProtocol.cpp @@ -0,0 +1,358 @@ +#include +#include +#include +#include +#include +#include +#include "ElGamal.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "NetDb.h" +#include "Tunnel.h" +#include "base64.h" +#include "Transports.h" +#include "I2NPProtocol.h" + +namespace i2p +{ + + I2NPMessage * NewI2NPMessage () + { + I2NPMessage * msg = new I2NPMessage; + msg->offset = 0; + msg->len = sizeof (I2NPHeader); + return msg; + } + + void DeleteI2NPMessage (I2NPMessage * msg) + { + delete msg; + } + + void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID) + { + static uint32_t msgID = 0; + I2NPHeader * header = msg->GetHeader (); + header->typeID = msgType; + if (replyMsgID) // for tunnel creation + header->msgID = htobe32 (replyMsgID); + else + { + header->msgID = htobe32 (msgID); + msgID++; + } + header->expiration = htobe64 (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number + int len = msg->len - sizeof (I2NPHeader); + header->size = htobe16 (len); + uint8_t hash[32]; + CryptoPP::SHA256().CalculateDigest(hash, msg->GetPayload (), len); + header->chks = hash[0]; + } + + I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) + { + I2NPMessage * msg = NewI2NPMessage (); + memcpy (msg->GetPayload (), buf, len); + msg->len += len; + FillI2NPMessageHeader (msg, msgType, replyMsgID); + return msg; + } + + I2NPMessage * CreateDeliveryStatusMsg () + { +#pragma pack(1) + struct + { + uint32_t msgID; + uint64_t timestamp; + } msg; +#pragma pack () + + msg.msgID = 0; + msg.timestamp = htobe64 (i2p::util::GetMillisecondsSinceEpoch ()); + return CreateI2NPMessage (eI2NPDeliveryStatus, (uint8_t *)&msg, sizeof (msg)); + } + + I2NPMessage * CreateDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID) + { +#pragma pack(1) + struct + { + uint8_t key[32]; + uint8_t from[32]; + uint8_t flags; + uint32_t replyTunnelID; + uint16_t size; + } msg; +#pragma pack () + + memcpy (msg.key, key, 32); + memcpy (msg.from, from, 32); + msg.flags = replyTunnelID ? 0x01 : 0; + msg.replyTunnelID = htobe32 (replyTunnelID); + msg.size = 0; + return CreateI2NPMessage (eI2NPDatabaseLookup, (uint8_t *)&msg, sizeof (msg)); + } + + I2NPMessage * CreateDatabaseStoreMsg () + { + I2NPMessage * m = NewI2NPMessage (); + I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)m->GetPayload (); + + memcpy (msg->key, context.GetRouterInfo ().GetIdentHash (), 32); + msg->type = 0; + msg->replyToken = 0; + msg->size = 0; + + CryptoPP::Gzip compressor; + compressor.Put ((uint8_t *)context.GetRouterInfo ().GetBuffer (), context.GetRouterInfo ().GetBufferLen ()); + compressor.MessageEnd(); + int size = compressor.MaxRetrievable (); + msg->size = htobe16 (size); + uint8_t * buf = m->GetPayload () + sizeof (I2NPDatabaseStoreMsg); + compressor.Get (buf, size); + m->len += sizeof (I2NPDatabaseStoreMsg) + size; // payload size + FillI2NPMessageHeader (m, eI2NPDatabaseStore); + + return m; + } + + void HandleDatabaseStoreMsg (uint8_t * buf, size_t len) + { + I2NPDatabaseStoreMsg * msg = (I2NPDatabaseStoreMsg *)buf; + if (msg->type) + { + LogPrint ("LeaseSet"); + i2p::data::netdb.AddLeaseSet (buf + sizeof (I2NPDatabaseStoreMsg)-2, len - sizeof (I2NPDatabaseStoreMsg)+2); + } + else + { + LogPrint ("RouterInfo"); + CryptoPP::Gunzip decompressor; + decompressor.Put (buf + sizeof (I2NPDatabaseStoreMsg), be16toh (msg->size)); + decompressor.MessageEnd(); + uint8_t uncompressed[1024]; + int size = decompressor.MaxRetrievable (); + decompressor.Get (uncompressed, size); + i2p::data::netdb.AddRouterInfo (uncompressed, size); + } + } + + void HandleDatabaseSearchReplyMsg (uint8_t * buf, size_t len) + { +#pragma pack(1) + struct + { + uint8_t key[32]; + uint8_t num; + } * msg; +#pragma pack() + msg = (decltype(msg))buf; + char key[48]; + int l = i2p::data::ByteStreamToBase64 (msg->key, 32, key, 48); + key[l] = 0; + LogPrint ("DatabaseSearchReply for ", key, " num=", (int)msg->num); + for (int i = 0; i < msg->num; i++) + { + char peerHash[48]; + int l1 = i2p::data::ByteStreamToBase64 (buf + sizeof (*msg) +i*32, 32, peerHash, 48); + peerHash[l1] = 0; + LogPrint (i,": ", peerHash); + + i2p::data::netdb.RequestDestination (msg->key, buf + sizeof (*msg) +i*32); + } + } + + I2NPBuildRequestRecordClearText CreateBuildRequestRecord ( + const uint8_t * ourIdent, uint32_t receiveTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey, + const uint8_t * replyKey, const uint8_t * replyIV, uint32_t nextMessageID, + bool isGateway, bool isEndpoint) + { + I2NPBuildRequestRecordClearText clearText; + clearText.receiveTunnel = htobe32 (receiveTunnelID); + clearText.nextTunnel = htobe32(nextTunnelID); + memcpy (clearText.layerKey, layerKey, 32); + memcpy (clearText.ivKey, ivKey, 32); + memcpy (clearText.replyKey, replyKey, 32); + memcpy (clearText.replyIV, replyIV, 16); + clearText.flag = 0; + if (isGateway) clearText.flag |= 0x80; + if (isEndpoint) clearText.flag |= 0x40; + memcpy (clearText.ourIdent, ourIdent, 32); + memcpy (clearText.nextIdent, nextIdent, 32); + clearText.requestTime = i2p::util::GetHoursSinceEpoch (); + clearText.nextMessageID = htobe32(nextMessageID); + return clearText; + } + + void EncryptBuildRequestRecord (const i2p::data::RouterInfo& router, + const I2NPBuildRequestRecordClearText& clearText, + I2NPBuildRequestRecordElGamalEncrypted& record) + { + i2p::crypto::ElGamalEncrypt (router.GetRouterIdentity ().publicKey, (uint8_t *)&clearText, sizeof(clearText), record.encrypted); + memcpy (record.toPeer, router.GetIdentHash (), 16); + } + + void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint ("VariableTunnelBuild ", num, " records"); + + i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID); + if (tunnel) + { + LogPrint ("VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + tunnel->HandleVariableTunnelBuildReplyMsg (buf, len); + } + else + { + I2NPBuildRequestRecordElGamalEncrypted * records = (I2NPBuildRequestRecordElGamalEncrypted *)(buf+1); + for (int i = 0; i < num; i++) + { + if (!memcmp (records[i].toPeer, i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint ("Record ",i," is ours"); + + I2NPBuildRequestRecordClearText clearText; + i2p::crypto::ElGamalDecrypt (i2p::context.GetPrivateKey (), records[i].encrypted, (uint8_t *)&clearText); + + i2p::tunnel::TransitTunnel * transitTunnel = + new i2p::tunnel::TransitTunnel ( + be32toh (clearText.receiveTunnel), + clearText.nextIdent, be32toh (clearText.nextTunnel), + clearText.layerKey, clearText.ivKey, + clearText.flag & 0x80, clearText.flag & 0x40); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + // replace record to reply + I2NPBuildResponseRecord * reply = (I2NPBuildResponseRecord *)(records + i); + reply->ret = 0; + //TODO: fill filler + CryptoPP::SHA256().CalculateDigest(reply->hash, reply->padding, sizeof (reply->padding) + 1); // + 1 byte of ret + // encrypt reply + CryptoPP::CBC_Mode::Encryption encryption; + for (int j = 0; j < num; j++) + { + encryption.SetKeyWithIV (clearText.replyKey, 32, clearText.replyIV); + encryption.ProcessData((uint8_t *)(records + j), (uint8_t *)(records + j), sizeof (records[j])); + } + + i2p::transports.SendMessage (clearText.nextIdent, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, be32toh (clearText.nextMessageID))); + return; + } + } + } + } + + void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + LogPrint ("VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID); + i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID); + if (tunnel) + { + tunnel->HandleVariableTunnelBuildReplyMsg (buf, len); + LogPrint ("Tunnel ", tunnel->GetTunnelID (), " has been created"); + } + else + LogPrint ("Pending tunnel for message ", replyMsgID, " not found"); + } + + + I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf) + { + I2NPMessage * msg = NewI2NPMessage (); + memcpy (msg->GetPayload (), buf, 1028); + msg->len += 1028; + FillI2NPMessageHeader (msg, eI2NPTunnelData); + return msg; + } + + I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) + { + I2NPMessage * msg = NewI2NPMessage (); + memcpy (msg->GetPayload () + 4, payload, 1024); + *(uint32_t *)(msg->GetPayload ()) = htobe32 (tunnelID); + msg->len += 1028; + FillI2NPMessageHeader (msg, eI2NPTunnelData); + return msg; + } + + + void HandleTunnelGatewayMsg (uint8_t * buf, size_t len) + { + TunnelGatewayHeader * header = (TunnelGatewayHeader *)buf; + uint32_t tunnelID = be32toh(header->tunnelID); + LogPrint ("TunnelGateway of ", (int)be16toh(header->length), + " bytes for tunnel ", (unsigned int)tunnelID); + i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + { + // TODO: we must submit message to tunnel + } + else + LogPrint ("Tunnel ", (unsigned int)tunnelID, " not found"); + } + + void HandleI2NPMessage (uint8_t * msg, size_t len) + { + I2NPHeader * header = (I2NPHeader *)msg; + uint32_t msgID = be32toh (header->msgID); + LogPrint ("I2NP msg received len=", len,", type=", (int)header->typeID, ", msgID=", (unsigned int)msgID); + + uint8_t * buf = msg + sizeof (I2NPHeader); + int size = be16toh (header->size); + switch (header->typeID) + { + case eI2NPTunnelData: // main message + LogPrint ("Unexpected TunnelData"); + break; + case eI2NPGarlic: + LogPrint ("Garlic"); + break; + case eI2NPDatabaseStore: + LogPrint ("DatabaseStore"); + HandleDatabaseStoreMsg (buf, size); + break; + case eI2NPDatabaseSearchReply: + LogPrint ("DatabaseSearchReply"); + HandleDatabaseSearchReplyMsg (buf, size); + break; + case eI2NPDeliveryStatus: + LogPrint ("DeliveryStatus"); + break; + case eI2NPVariableTunnelBuild: + LogPrint ("VariableTunnelBuild"); + HandleVariableTunnelBuildMsg (msgID, buf, size); + break; + case eI2NPTunnelGateway: + LogPrint ("TunnelGateway"); + HandleTunnelGatewayMsg (buf, size); + break; + case eI2NPVariableTunnelBuildReply: + LogPrint ("VariableTunnelBuildReply"); + HandleVariableTunnelBuildReplyMsg (msgID, buf, size); + break; + default: + ; + } + } + + void HandleI2NPMessage (I2NPMessage * msg) + { + if (msg) + { + if (msg->GetHeader ()->typeID == eI2NPTunnelData) + { + LogPrint ("TunnelData"); + i2p::tunnel::tunnels.PostTunnelData (msg); + } + else + { + HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + DeleteI2NPMessage (msg); + } + } + } +} diff --git a/I2NPProtocol.h b/I2NPProtocol.h new file mode 100644 index 00000000..c9a2cda3 --- /dev/null +++ b/I2NPProtocol.h @@ -0,0 +1,127 @@ +#ifndef I2NP_PROTOCOL_H__ +#define I2NP_PROTOCOL_H__ + +#include +#include +#include "RouterInfo.h" + +namespace i2p +{ +#pragma pack (1) + + struct I2NPHeader + { + uint8_t typeID; + uint32_t msgID; + uint64_t expiration; + uint16_t size; + uint8_t chks; + }; + + struct I2NPDatabaseStoreMsg + { + uint8_t key[32]; + uint8_t type; + uint32_t replyToken; + uint16_t size; + }; + + struct I2NPBuildRequestRecordClearText + { + uint32_t receiveTunnel; + uint8_t ourIdent[32]; + uint32_t nextTunnel; + uint8_t nextIdent[32]; + uint8_t layerKey[32]; + uint8_t ivKey[32]; + uint8_t replyKey[32]; + uint8_t replyIV[16]; + uint8_t flag; + uint32_t requestTime; + uint32_t nextMessageID; + uint8_t filler[29]; + }; + + struct I2NPBuildResponseRecord + { + uint8_t hash[32]; + uint8_t padding[495]; + uint8_t ret; + }; + + struct I2NPBuildRequestRecordElGamalEncrypted + { + uint8_t toPeer[16]; + uint8_t encrypted[512]; + }; + + struct TunnelGatewayHeader + { + uint32_t tunnelID; + uint16_t length; + }; + + +#pragma pack () + + enum I2NPMessageType + { + eI2NPDatabaseStore = 1, + eI2NPDatabaseLookup = 2, + eI2NPDatabaseSearchReply = 3, + eI2NPDeliveryStatus = 10, + eI2NPGarlic = 11, + eI2NPTunnelData = 18, + eI2NPTunnelGateway = 19, + eI2NPData = 20, + eI2NPVariableTunnelBuild = 23, + eI2NPVariableTunnelBuildReply = 24 + }; + + const int NTCP_MAX_MESSAGE_SIZE = 16384; + struct I2NPMessage + { + uint8_t buf[NTCP_MAX_MESSAGE_SIZE]; + size_t len, offset; + + I2NPHeader * GetHeader () { return (I2NPHeader *)(buf + offset); }; + uint8_t * GetPayload () { return buf + offset + sizeof(I2NPHeader); }; + uint8_t * GetBuffer () { return buf + offset; }; + size_t GetLength () const { return len - offset; }; + }; + I2NPMessage * NewI2NPMessage (); + void DeleteI2NPMessage (I2NPMessage * msg); + void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0); + I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); + + I2NPMessage * CreateDeliveryStatusMsg (); + I2NPMessage * CreateDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID); + I2NPMessage * CreateDatabaseStoreMsg (); + + void HandleDatabaseStoreMsg (uint8_t * buf, size_t len); + void HandleDatabaseSearchReplyMsg (uint8_t * buf, size_t len); + + + I2NPBuildRequestRecordClearText CreateBuildRequestRecord ( + const uint8_t * ourIdent, uint32_t receiveTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey, + const uint8_t * replyKey, const uint8_t * replyIV, uint32_t nextMessageID, + bool isGateway, bool isEndpoint); + void EncryptBuildRequestRecord (const i2p::data::RouterInfo& router, + const I2NPBuildRequestRecordClearText& clearText, + I2NPBuildRequestRecordElGamalEncrypted& record); + + void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); + void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); + + I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf); + I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); + + void HandleTunnelGatewayMsg (uint8_t * buf, size_t len); + + void HandleI2NPMessage (uint8_t * msg, size_t len); + void HandleI2NPMessage (I2NPMessage * msg); +} + +#endif