i2pd/libi2pd/SSUData.cpp
Anatolii Cherednichenko 55534ea002 Reformat code
2022-08-30 02:11:28 +03:00

448 lines
20 KiB
C++

/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <stdlib.h>
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "SSU.h"
#include "SSUData.h"
namespace i2p {
namespace transport {
void IncompleteMessage::AttachNextFragment(const uint8_t *fragment, size_t fragmentSize) {
if (msg->len + fragmentSize > msg->maxLen) {
LogPrint(eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough");
auto newMsg = NewI2NPMessage();
*newMsg = *msg;
msg = newMsg;
}
if (msg->Concat(fragment, fragmentSize) < fragmentSize)
LogPrint(eLogError, "SSU: I2NP buffer overflow ", msg->maxLen);
nextFragmentNum++;
}
SSUData::SSUData(SSUSession &session) :
m_Session(session), m_ResendTimer(session.GetService()),
m_MaxPacketSize(session.IsV6() ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE),
m_PacketSize(m_MaxPacketSize), m_LastMessageReceivedTime(0) {
}
SSUData::~SSUData() {
}
void SSUData::Start() {
}
void SSUData::Stop() {
m_ResendTimer.cancel();
m_IncompleteMessages.clear();
m_SentMessages.clear();
m_ReceivedMessages.clear();
}
void SSUData::AdjustPacketSize(std::shared_ptr<const i2p::data::RouterInfo> remoteRouter) {
if (!remoteRouter) return;
auto ssuAddress = remoteRouter->GetSSUAddress();
if (ssuAddress && ssuAddress->ssu->mtu) {
if (m_Session.IsV6())
m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
else
m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
if (m_PacketSize > 0) {
// make sure packet size multiple of 16
m_PacketSize >>= 4;
m_PacketSize <<= 4;
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
LogPrint(eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize);
} else {
LogPrint(eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu);
m_PacketSize = m_MaxPacketSize;
}
}
}
void SSUData::UpdatePacketSize(const i2p::data::IdentHash &remoteIdent) {
auto routerInfo = i2p::data::netdb.FindRouter(remoteIdent);
if (routerInfo)
AdjustPacketSize(routerInfo);
}
void SSUData::ProcessSentMessageAck(uint32_t msgID) {
auto it = m_SentMessages.find(msgID);
if (it != m_SentMessages.end()) {
m_SentMessages.erase(it);
if (m_SentMessages.empty())
m_ResendTimer.cancel();
}
}
void SSUData::ProcessAcks(uint8_t *&buf, uint8_t flag) {
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) {
// explicit ACKs
uint8_t numAcks = *buf;
buf++;
for (int i = 0; i < numAcks; i++)
ProcessSentMessageAck(bufbe32toh(buf + i * 4));
buf += numAcks * 4;
}
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) {
// explicit ACK bitfields
uint8_t numBitfields = *buf;
buf++;
for (int i = 0; i < numBitfields; i++) {
uint32_t msgID = bufbe32toh(buf);
buf += 4; // msgID
auto it = m_SentMessages.find(msgID);
// process individual Ack bitfields
bool isNonLast = false;
int fragment = 0;
do {
uint8_t bitfield = *buf;
isNonLast = bitfield & 0x80;
bitfield &= 0x7F; // clear MSB
if (bitfield && it != m_SentMessages.end()) {
int numSentFragments = it->second->fragments.size();
// process bits
uint8_t mask = 0x01;
for (int j = 0; j < 7; j++) {
if (bitfield & mask) {
if (fragment < numSentFragments)
it->second->fragments[fragment] = nullptr;
}
fragment++;
mask <<= 1;
}
}
buf++;
} while (isNonLast);
}
}
}
void SSUData::ProcessFragments(uint8_t *buf) {
uint8_t numFragments = *buf; // number of fragments
buf++;
for (int i = 0; i < numFragments; i++) {
uint32_t msgID = bufbe32toh(buf); // message ID
buf += 4;
uint8_t frag[4] = {0};
memcpy(frag + 1, buf, 3);
buf += 3;
uint32_t fragmentInfo = bufbe32toh(frag); // fragment info
uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13
bool isLast = fragmentInfo & 0x010000; // bit 16
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) {
LogPrint(eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size");
return;
}
// find message with msgID
auto it = m_IncompleteMessages.find(msgID);
if (it == m_IncompleteMessages.end()) {
// create new message
auto msg = NewI2NPShortMessage();
msg->len -= I2NP_SHORT_HEADER_SIZE;
it = m_IncompleteMessages.insert(std::make_pair(msgID,
m_Session.GetServer().GetIncompleteMessagesPool().AcquireShared(
std::move(msg)))).first;
}
auto &incompleteMessage = it->second;
// mark fragment as received
if (fragmentNum < 64)
incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum);
else
LogPrint(eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64");
// handle current fragment
if (fragmentNum == incompleteMessage->nextFragmentNum) {
// expected fragment
incompleteMessage->AttachNextFragment(buf, fragmentSize);
if (!isLast && !incompleteMessage->savedFragments.empty()) {
// try saved fragments
for (auto it1 = incompleteMessage->savedFragments.begin();
it1 != incompleteMessage->savedFragments.end();) {
auto &savedFragment = *it1;
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) {
incompleteMessage->AttachNextFragment(savedFragment->buf, savedFragment->len);
isLast = savedFragment->isLast;
incompleteMessage->savedFragments.erase(it1++);
} else
break;
}
if (isLast)
LogPrint(eLogDebug, "SSU: Message ", msgID, " complete");
}
} else {
if (fragmentNum < incompleteMessage->nextFragmentNum)
// duplicate fragment
LogPrint(eLogWarning, "SSU: Duplicate fragment ", (int) fragmentNum, " of message ", msgID,
", ignored");
else {
// missing fragment
LogPrint(eLogWarning, "SSU: Missing fragments from ", (int) incompleteMessage->nextFragmentNum,
" to ", fragmentNum - 1, " of message ", msgID);
auto savedFragment = m_Session.GetServer().GetFragmentsPool().AcquireShared(fragmentNum, buf,
fragmentSize,
isLast);
if (incompleteMessage->savedFragments.insert(savedFragment).second)
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch();
else
LogPrint(eLogWarning, "SSU: Fragment ", (int) fragmentNum, " of message ", msgID,
" already saved");
}
isLast = false;
}
if (isLast) {
// delete incomplete message
auto msg = incompleteMessage->msg;
incompleteMessage->msg = nullptr;
m_IncompleteMessages.erase(msgID);
// process message
SendMsgAck(msgID);
msg->FromSSU(msgID);
if (m_Session.GetState() == eSessionStateEstablished) {
if (!m_ReceivedMessages.count(msgID)) {
m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch();
m_ReceivedMessages.emplace(msgID, m_LastMessageReceivedTime);
if (!msg->IsExpired()) {
m_Handler.PutNextMessage(std::move(msg));
} else
LogPrint(eLogDebug, "SSU: message expired");
} else
LogPrint(eLogWarning, "SSU: Message ", msgID, " already received");
} else {
// we expect DeliveryStatus
if (msg->GetTypeID() == eI2NPDeliveryStatus) {
LogPrint(eLogDebug, "SSU: session established");
m_Session.Established();
} else
LogPrint(eLogError, "SSU: unexpected message ", (int) msg->GetTypeID());
}
} else
SendFragmentAck(msgID, incompleteMessage->receivedFragmentsBits);
buf += fragmentSize;
}
}
void SSUData::FlushReceivedMessage() {
m_Handler.Flush();
}
void SSUData::ProcessMessage(uint8_t *buf, size_t len) {
//uint8_t * start = buf;
uint8_t flag = *buf;
buf++;
LogPrint(eLogDebug, "SSU: Process data, flags=", (int) flag, ", len=", len);
// process acks if presented
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
ProcessAcks(buf, flag);
// extended data if presented
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) {
uint8_t extendedDataSize = *buf;
buf++; // size
LogPrint(eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present");
buf += extendedDataSize;
}
// process data
ProcessFragments(buf);
}
void SSUData::Send(std::shared_ptr<i2p::I2NPMessage> msg) {
uint32_t msgID = msg->ToSSU();
if (m_SentMessages.find(msgID) != m_SentMessages.end()) {
LogPrint(eLogWarning, "SSU: message ", msgID, " already sent");
return;
}
if (m_SentMessages.empty()) // schedule resend at first message only
ScheduleResend();
auto ret = m_SentMessages.emplace(msgID, m_Session.GetServer().GetSentMessagesPool().AcquireShared());
auto &sentMessage = ret.first->second;
if (ret.second) {
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch() + RESEND_INTERVAL;
sentMessage->numResends = 0;
}
auto &fragments = sentMessage->fragments;
size_t payloadSize =
m_PacketSize - sizeof(SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
size_t len = msg->GetLength();
uint8_t *msgBuf = msg->GetSSUHeader();
uint32_t fragmentNum = 0;
while (len > 0 && fragmentNum <= 127) {
auto fragment = m_Session.GetServer().GetFragmentsPool().AcquireShared();
fragment->fragmentNum = fragmentNum;
uint8_t *payload = fragment->buf + sizeof(SSUHeader);
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
payload++;
*payload = 1; // always 1 message fragment per message
payload++;
htobe32buf(payload, msgID);
payload += 4;
bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max
size_t size = isLast ? len : payloadSize;
uint32_t fragmentInfo = (fragmentNum << 17);
if (isLast)
fragmentInfo |= 0x010000;
fragmentInfo |= size;
fragmentInfo = htobe32(fragmentInfo);
memcpy(payload, (uint8_t * )(&fragmentInfo) + 1, 3);
payload += 3;
memcpy(payload, msgBuf, size);
size += payload - fragment->buf;
uint8_t rem = size & 0x0F;
if (rem) // make sure 16 bytes boundary
{
auto padding = 16 - rem;
memset(fragment->buf + size, 0, padding);
size += padding;
}
fragment->len = size;
fragments.push_back(fragment);
// encrypt message with session key
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18];
m_Session.FillHeaderAndEncrypt(PAYLOAD_TYPE_DATA, fragment->buf, size, buf);
try {
m_Session.Send(buf, size);
}
catch (boost::system::system_error &ec) {
LogPrint(eLogWarning, "SSU: Can't send data fragment ", ec.what());
}
if (!isLast) {
len -= payloadSize;
msgBuf += payloadSize;
} else
len = 0;
fragmentNum++;
}
}
void SSUData::SendMsgAck(uint32_t msgID) {
uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16
uint8_t *payload = buf + sizeof(SSUHeader);
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACKs
payload++;
htobe32buf(payload, msgID); // msgID
payload += 4;
*payload = 0; // number of fragments
// encrypt message with session key
m_Session.FillHeaderAndEncrypt(PAYLOAD_TYPE_DATA, buf, 48);
m_Session.Send(buf, 48);
}
void SSUData::SendFragmentAck(uint32_t msgID, uint64_t bits) {
if (!bits) return;
uint8_t buf[64 + 18] = {0};
uint8_t *payload = buf + sizeof(SSUHeader);
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACK bitfields
payload++;
// one ack
*(uint32_t * )(payload) = htobe32(msgID); // msgID
payload += 4;
size_t len = 0;
while (bits) {
*payload = (bits & 0x7F); // next 7 bits
bits >>= 7;
if (bits) *payload &= 0x80; // 0x80 means non-last
payload++;
len++;
}
*payload = 0; // number of fragments
len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4
// encrypt message with session key
m_Session.FillHeaderAndEncrypt(PAYLOAD_TYPE_DATA, buf, len);
m_Session.Send(buf, len);
}
void SSUData::ScheduleResend() {
m_ResendTimer.cancel();
m_ResendTimer.expires_from_now(boost::posix_time::seconds(RESEND_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait(
[s](const boost::system::error_code &ecode) { s->m_Data.HandleResendTimer(ecode); });
}
void SSUData::HandleResendTimer(const boost::system::error_code &ecode) {
if (ecode != boost::asio::error::operation_aborted) {
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18];
uint32_t ts = i2p::util::GetSecondsSinceEpoch();
int numResent = 0;
for (auto it = m_SentMessages.begin(); it != m_SentMessages.end();) {
if (ts >= it->second->nextResendTime) {
if (it->second->numResends < MAX_NUM_RESENDS) {
for (auto &f: it->second->fragments)
if (f) {
try {
m_Session.FillHeaderAndEncrypt(PAYLOAD_TYPE_DATA, f->buf, f->len, buf);
m_Session.Send(buf, f->len); // resend
numResent++;
}
catch (boost::system::system_error &ec) {
LogPrint(eLogWarning, "SSU: Can't resend message ", it->first,
" data fragment: ", ec.what());
}
}
it->second->numResends++;
it->second->nextResendTime += it->second->numResends * RESEND_INTERVAL;
++it;
} else {
LogPrint(eLogInfo, "SSU: message ", it->first, " has not been ACKed after ",
MAX_NUM_RESENDS, " attempts, deleted");
it = m_SentMessages.erase(it);
}
} else
++it;
}
if (m_SentMessages.empty()) return; // nothing to resend
if (numResent < MAX_OUTGOING_WINDOW_SIZE)
ScheduleResend();
else {
LogPrint(eLogError, "SSU: resend window exceeds max size. Session terminated");
m_Session.Close();
}
}
}
void SSUData::CleanUp(uint64_t ts) {
for (auto it = m_IncompleteMessages.begin(); it != m_IncompleteMessages.end();) {
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) {
LogPrint(eLogWarning, "SSU: message ", it->first, " was not completed in ",
INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted");
it = m_IncompleteMessages.erase(it);
} else
++it;
}
if (m_ReceivedMessages.size() > MAX_NUM_RECEIVED_MESSAGES ||
ts > m_LastMessageReceivedTime + DECAY_INTERVAL)
// decay
m_ReceivedMessages.clear();
else {
// delete old received messages
for (auto it = m_ReceivedMessages.begin(); it != m_ReceivedMessages.end();) {
if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT)
it = m_ReceivedMessages.erase(it);
else
++it;
}
}
}
}
}