From 84301daeb87ef7f3189c4614f5bff502790bf955 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Nov 2013 18:19:49 -0500 Subject: [PATCH] transit tunnel --- TransitTunnel.cpp | 85 +++++++++++++++++++++++ TransitTunnel.h | 53 +++++++++++++++ TunnelBase.h | 26 +++++++ TunnelEndpoint.cpp | 159 +++++++++++++++++++++++++++++++++++++++++++ TunnelEndpoint.h | 31 +++++++++ TunnelGateway.cpp | 166 +++++++++++++++++++++++++++++++++++++++++++++ TunnelGateway.h | 41 +++++++++++ 7 files changed, 561 insertions(+) create mode 100644 TransitTunnel.cpp create mode 100644 TransitTunnel.h create mode 100644 TunnelBase.h create mode 100644 TunnelEndpoint.cpp create mode 100644 TunnelEndpoint.h create mode 100644 TunnelGateway.cpp create mode 100644 TunnelGateway.h diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp new file mode 100644 index 00000000..bd50ae92 --- /dev/null +++ b/TransitTunnel.cpp @@ -0,0 +1,85 @@ +#include +#include "Log.h" +#include "RouterContext.h" +#include "I2NPProtocol.h" +#include "Tunnel.h" +#include "Transports.h" +#include "TransitTunnel.h" + +namespace i2p +{ +namespace tunnel +{ + TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey, + bool isGateway, bool isEndpoint) + { + memcpy (m_LayerKey, layerKey, 32); + memcpy (m_IVKey, ivKey, 32); + memcpy (m_NextIdent, nextIdent, 32); + m_IsGateway = isGateway; + m_IsEndpoint = isEndpoint; + m_TunnelID = receiveTunnelID; + m_NextTunnelID = nextTunnelID; + if (m_IsEndpoint) + LogPrint ("TransitTunnel endpoint: ", m_TunnelID, " created"); + else if (m_IsGateway) + LogPrint ("TransitTunnel gateway: ", m_TunnelID, " created"); + else + LogPrint ("TransitTunnel: ",m_TunnelID,"->", m_NextTunnelID, " created"); + } + + void TransitTunnel::Encrypt (uint8_t * payload) + { + m_ECBEncryption.SetKey (m_IVKey, 32); + m_ECBEncryption.ProcessData(payload, payload, 16); // iv + + m_CBCEncryption.SetKeyWithIV (m_LayerKey, 32, payload); + m_CBCEncryption.ProcessData(payload + 16, payload + 16, 1008); // payload + + m_ECBEncryption.SetKey (m_IVKey, 32); + m_ECBEncryption.ProcessData(payload, payload, 16); // double iv encryption + + } + + void TransitTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + { + Encrypt (tunnelMsg->GetPayload () + 4); + + if (m_IsEndpoint) + { + LogPrint ("TransitTunnel endpoint for ", m_TunnelID); + m_Endpoint.HandleDecryptedTunnelDataMsg (tunnelMsg); + } + else + { + LogPrint ("TransitTunnel: ",m_TunnelID,"->", m_NextTunnelID); + *(uint32_t *)(tunnelMsg->GetPayload ()) = htobe32 (m_NextTunnelID); + FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); + + i2p::transports.SendMessage (m_NextIdent, tunnelMsg); + } + } + + void TransitTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg) + { + if (m_IsGateway) + { + m_Gateway.PutI2NPMsg (gwHash, gwTunnel, msg); + auto tunnelMsgs = m_Gateway.GetTunnelDataMsgs (m_NextTunnelID); + for (auto tunnelMsg : tunnelMsgs) + { + Encrypt (tunnelMsg->GetPayload () + 4); + FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); + i2p::transports.SendMessage (m_NextIdent, tunnelMsg); + } + } + else + { + LogPrint ("We are not a gateway for transit tunnel ", m_TunnelID); + i2p::DeleteI2NPMessage (msg); + } + } +} +} \ No newline at end of file diff --git a/TransitTunnel.h b/TransitTunnel.h new file mode 100644 index 00000000..8253b47f --- /dev/null +++ b/TransitTunnel.h @@ -0,0 +1,53 @@ +#ifndef TRANSIT_TUNNEL_H__ +#define TRANSIT_TUNNEL_H__ + +#include +#include +#include +#include "I2NPProtocol.h" +#include "TunnelEndpoint.h" +#include "TunnelGateway.h" + +namespace i2p +{ +namespace tunnel +{ + class TransitTunnel + { + public: + + TransitTunnel (uint32_t receiveTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey, + bool isGateway, bool isEndpoint); + + void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); + void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg); + + uint32_t GetTunnelID () const { return m_TunnelID; }; + bool IsGateway () const { return m_IsGateway; }; + bool IsEndpoint () const { return m_IsEndpoint; }; + bool IsParticipant () const { return !IsGateway () && !IsEndpoint (); }; + + private: + + void Encrypt (uint8_t * payload); + + private: + + uint32_t m_TunnelID, m_NextTunnelID; + uint8_t m_NextIdent[32]; + uint8_t m_LayerKey[32]; + uint8_t m_IVKey[32]; + bool m_IsGateway, m_IsEndpoint; + + TunnelEndpoint m_Endpoint; + TunnelGatewayBuffer m_Gateway; + + CryptoPP::ECB_Mode::Encryption m_ECBEncryption; + CryptoPP::CBC_Mode::Encryption m_CBCEncryption; + }; +} +} + +#endif diff --git a/TunnelBase.h b/TunnelBase.h new file mode 100644 index 00000000..e9d45acd --- /dev/null +++ b/TunnelBase.h @@ -0,0 +1,26 @@ +#ifndef TUNNEL_BASE_H__ +#define TUNNEL_BASE_H__ + +#include + +namespace i2p +{ +namespace tunnel +{ + enum TunnelDeliveryType + { + eDeliveryTypeLocal = 0, + eDeliveryTypeTunnel = 1, + eDeliveryTypeRouter = 2 + }; + struct TunnelMessageBlock + { + TunnelDeliveryType deliveryType; + uint32_t tunnelID; + uint8_t hash[32]; + I2NPMessage * data; + }; +} +} + +#endif diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp new file mode 100644 index 00000000..4c7a0749 --- /dev/null +++ b/TunnelEndpoint.cpp @@ -0,0 +1,159 @@ +#include +#include "Log.h" +#include "I2NPProtocol.h" +#include "Transports.h" +#include "TunnelEndpoint.h" + +namespace i2p +{ +namespace tunnel +{ + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (I2NPMessage * msg) + { + uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16 + uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, 1004); // witout checksum + if (zero) + { + LogPrint ("TunnelMessage: zero found at ", (int)(zero-decrypted)); + uint8_t * fragment = zero + 1; + while (fragment < decrypted + 1008) + { + uint8_t flag = fragment[0]; + fragment++; + + bool isFollowOnFragment = flag & 0x80, isLastFragment = true; + uint32_t msgID = 0; + TunnelMessageBlock m; + if (!isFollowOnFragment) + { + // first fragment + + m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); + switch (m.deliveryType) + { + case eDeliveryTypeLocal: // 0 + LogPrint ("Delivery type local"); + break; + case eDeliveryTypeTunnel: // 1 + LogPrint ("Delivery type tunnel"); + m.tunnelID = be32toh (*(uint32_t *)fragment); + fragment += 4; // tunnelID + memcpy (m.hash, fragment, 32); + fragment += 32; // hash + break; + case eDeliveryTypeRouter: // 2 + LogPrint ("Delivery type router"); + memcpy (m.hash, fragment, 32); + fragment += 32; // to hash + break; + default: + ; + } + + bool isFragmented = flag & 0x08; + if (isFragmented) + { + // Message ID + msgID = be32toh (*(uint32_t *)fragment); + fragment += 4; + LogPrint ("Fragmented message ", msgID); + isLastFragment = false; + } + } + else + { + // follow on + msgID = be32toh (*(uint32_t *)fragment); // MessageID + fragment += 4; + int fragmentNum = (flag >> 1) & 0x3F; // 6 bits + isLastFragment = flag & 0x01; + LogPrint ("Follow on fragment ", fragmentNum, " of message ", msgID, isLastFragment ? " last" : " non-last"); + } + + uint16_t size = be16toh (*(uint16_t *)fragment); + fragment += 2; + LogPrint ("Fragment size=", (int)size); + + msg->offset = fragment - msg->buf; + msg->len = msg->offset + size; + bool isLastMessage = false; + if (fragment + size < decrypted + 1008) + { + // this is not last message. we have to copy it + m.data = NewI2NPMessage (); + m.data->offset += sizeof (TunnelGatewayHeader); // reserve room for TunnelGateway header + m.data->len += sizeof (TunnelGatewayHeader); + *(m.data) = *msg; + } + else + { + m.data = msg; + isLastMessage = true; + } + + if (!isFollowOnFragment && isLastFragment) + HandleNextMessage (m); + else + { + if (msgID) // msgID is presented, assume message is fragmented + { + if (!isFollowOnFragment) // create new incomlete message + m_IncompleteMessages[msgID] = m; + else + { + auto it = m_IncompleteMessages.find (msgID); + if (it != m_IncompleteMessages.end()) + { + I2NPMessage * incompleteMessage = it->second.data; + memcpy (incompleteMessage->buf + incompleteMessage->len, fragment, size); // concatenate fragment + incompleteMessage->len += size; + // TODO: check fragmentNum sequence + if (isLastFragment) + { + // message complete + HandleNextMessage (it->second); + m_IncompleteMessages.erase (it); + } + } + else + LogPrint ("First fragment of message ", msgID, " not found. Discarded"); + + if (isLastMessage) + // last message is follow-on fragment + // not passed to anywhere because first fragment + i2p::DeleteI2NPMessage (msg); + } + } + else + LogPrint ("Message is fragmented, but msgID is not presented"); + } + + fragment += size; + } + } + else + { + LogPrint ("TunnelMessage: zero not found"); + i2p::DeleteI2NPMessage (msg); + } + } + + void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) + { + switch (msg.deliveryType) + { + case eDeliveryTypeLocal: + i2p::HandleI2NPMessage (msg.data); + break; + case eDeliveryTypeTunnel: + i2p::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); + break; + case eDeliveryTypeRouter: + i2p::transports.SendMessage (msg.hash, msg.data); + break; + default: + ; + }; + } +} +} diff --git a/TunnelEndpoint.h b/TunnelEndpoint.h new file mode 100644 index 00000000..405780df --- /dev/null +++ b/TunnelEndpoint.h @@ -0,0 +1,31 @@ +#ifndef TUNNEL_ENDPOINT_H__ +#define TUNNEL_ENDPOINT_H__ + +#include +#include +#include +#include "I2NPProtocol.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + class TunnelEndpoint + { + public: + + void HandleDecryptedTunnelDataMsg (I2NPMessage * msg); + + private: + + void HandleNextMessage (const TunnelMessageBlock& msg); + + private: + + std::map m_IncompleteMessages; + }; +} +} + +#endif diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp new file mode 100644 index 00000000..8333fb98 --- /dev/null +++ b/TunnelGateway.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include "RouterContext.h" +#include "TunnelGateway.h" + +namespace i2p +{ +namespace tunnel +{ + void TunnelGatewayBuffer::PutI2NPMsg (const uint8_t * gwHash, uint32_t gwTunnel, I2NPMessage * msg) + { + TunnelMessageBlockExt * block = new TunnelMessageBlockExt; + block->deliveryInstructionsLen = 1; // flag + if (gwHash) + { + block->deliveryInstructionsLen = 32; // hash + memcpy (block->hash, gwHash, 32); + if (gwTunnel) + { + block->deliveryType = eDeliveryTypeTunnel; + block->deliveryInstructionsLen += 4; // tunnelID + block->tunnelID = gwTunnel; + } + else + block->deliveryType = eDeliveryTypeRouter; + } + else + block->deliveryType = eDeliveryTypeLocal; + // we don't reserve 4 bytes for msgID because we don't if it fits + block->totalLen = block->deliveryInstructionsLen + msg->GetLength (); + block->data = msg; + m_I2NPMsgs.push_back (block); + } + + std::vector TunnelGatewayBuffer::GetTunnelDataMsgs (uint32_t tunnelID) + { + std::vector res; + int cnt = m_I2NPMsgs.size (), pos = 0, prev = 0; + m_NextOffset = 0; + if (cnt > 0) + { + size_t size = 0; + while (pos < cnt) + { + TunnelMessageBlockExt * block = m_I2NPMsgs[pos]; + if (size + block->totalLen >= 1003) // 1003 = 1008 - checksum - zero + { + // we have to make sure if we can put delivery instructions + msgID of last message + if (size + block->deliveryInstructionsLen + 4 > 1003) + { + // we have to exclude last message + pos--; + } + else + size = 1003; + res.push_back (CreateNextTunnelMessage (tunnelID, prev, pos, size)); + prev = pos; + } + else + size += block->totalLen; + pos++; + } + res.push_back (CreateNextTunnelMessage (tunnelID, prev, pos, size)); // last message + for (auto m: m_I2NPMsgs) + delete m; + m_I2NPMsgs.clear (); + } + + return res; + } + + size_t TunnelGatewayBuffer::CreateFirstFragment (TunnelMessageBlockExt * block, uint8_t * buf, size_t len) + { + if (block->deliveryInstructionsLen > len) return 0; // can't put even delivery instructions + size_t ret = 1; + buf[0] = block->deliveryType << 5; // flag + if (block->deliveryType == eDeliveryTypeTunnel) + { + *(uint32_t *)(buf + ret) = htobe32 (block->tunnelID); + ret += 4; + } + if (block->deliveryType == eDeliveryTypeTunnel || block->deliveryType == eDeliveryTypeRouter) + { + memcpy (buf + ret, block->hash, 32); + ret += 32; + } + size_t size = block->data->GetLength (); + if (block->totalLen > len) // entire message doesn't fit + { + if (ret + 4 > len) return 0; // can't put delivery instructions with msgID + buf[0] |= 0x08; // set fragmented bit + m_NextMsgID = block->data->GetHeader ()->msgID; + *(uint32_t *)(buf + ret) = m_NextMsgID; + ret += 4; // msgID + m_NextSeqn = 1; + size -= (block->totalLen - len); + m_NextOffset = size; + } + *(uint16_t *)(buf + ret) = htobe16 (size); // size + ret += 2; + memcpy (buf + ret, block->data->GetBuffer (), size); + ret += size; + return ret; + } + + size_t TunnelGatewayBuffer::CreateFollowOnFragment (TunnelMessageBlockExt * block, uint8_t * buf, size_t len) + { + int ret = 0; + buf[0] = 0x80 | (m_NextSeqn << 1);// follow-on flag and seqn + size_t fragmentLen = len - 7; // 7 bytes of header + if (fragmentLen >= block->totalLen - m_NextOffset) + { + // fragment fits + fragmentLen = block->totalLen - m_NextOffset; + buf[0] |= 0x01; // last fragment + } + else + m_NextSeqn++; + + *(uint32_t *)(buf + 1) = m_NextMsgID; // msgID + *(uint16_t *)(buf + 5) = htobe16 (fragmentLen); // size + memcpy (buf + 7, block->data->GetBuffer () + m_NextOffset, fragmentLen); + + m_NextOffset += fragmentLen; + ret += fragmentLen + 7; + + return ret; + } + + I2NPMessage * TunnelGatewayBuffer::CreateNextTunnelMessage (uint32_t tunnelID, + int from, int to, size_t size) + { + I2NPMessage * tunnelMsg = NewI2NPMessage (); + uint8_t * buf = tunnelMsg->GetPayload (); + *(uint32_t *)(buf) = htobe32 (tunnelID); + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (buf + 4, 16); // original IV + memcpy (buf + 1028, buf + 4, 16); // copy IV for checksum + size_t zero = 1028 - size; + buf[zero] = 0; // zero + buf += zero; + for (int i = from; i <= to; i++) + { + TunnelMessageBlockExt * block = m_I2NPMsgs[i]; + size_t s = CreateFirstFragment (block, buf, size); + if (s < size) + { + size -= s; + buf += s; + } + else + break; + } + uint8_t hash[32]; + CryptoPP::SHA256().CalculateDigest(hash, buf+zero+1, size+16); + memcpy (buf+20, hash, 4); // checksum + if (zero > 25) + memset (buf+24, 1, zero-25); // padding + + // we can't fill message header yet because encryption is required + return tunnelMsg; + } +} +} + diff --git a/TunnelGateway.h b/TunnelGateway.h new file mode 100644 index 00000000..71a24336 --- /dev/null +++ b/TunnelGateway.h @@ -0,0 +1,41 @@ +#ifndef TUNNEL_GATEWAY_H__ +#define TUNNEL_GATEWAY_H__ + +#include +#include +#include "I2NPProtocol.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + class TunnelGatewayBuffer + { + struct TunnelMessageBlockExt: public TunnelMessageBlock + { + size_t deliveryInstructionsLen, totalLen; + }; + + public: + + void PutI2NPMsg (const uint8_t * gwHash, uint32_t gwTunnel, I2NPMessage * msg); + std::vector GetTunnelDataMsgs (uint32_t tunnelID); + + private: + + size_t CreateFirstFragment (TunnelMessageBlockExt * block, uint8_t * buf, size_t len); + size_t CreateFollowOnFragment (TunnelMessageBlockExt * block, uint8_t * buf, size_t len); + I2NPMessage * CreateNextTunnelMessage (uint32_t tunnelID, int from, int to, size_t size); + + private: + + std::vector m_I2NPMsgs; + // for fragmented messages + size_t m_NextOffset, m_NextSeqn; + uint32_t m_NextMsgID; + }; +} +} + +#endif