/* * 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 #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 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 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; } } } } }