diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 467377a8..fbec0a5b 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -32,7 +32,7 @@ namespace transport TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), m_Server (server), m_Address (addr), m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), m_SendPacketNum (0), m_ReceivePacketNum (0), - m_IsDataReceived (false), m_WindowSize (SSU2_MAX_WINDOW_SIZE) + m_IsDataReceived (false), m_WindowSize (SSU2_MAX_WINDOW_SIZE), m_RelayTag (0) { m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); if (in_RemoteRouter && m_Address) @@ -70,6 +70,8 @@ namespace transport m_State = eSSU2SessionStateTerminated; transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveSession (m_SourceConnID); + if (m_RelayTag) + m_Server.RemoveRelay (m_RelayTag); m_SendQueue.clear (); LogPrint (eLogDebug, "SSU2: Session terminated"); } @@ -338,6 +340,13 @@ namespace transport htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); size_t payloadSize = 7; payloadSize += CreateAddressBlock (m_RemoteEndpoint, payload + payloadSize, 64 - payloadSize); + if (m_RelayTag) + { + payload[payloadSize] = eSSU2BlkRelayTag; + htobe16buf (payload + payloadSize + 1, 4); + htobe32buf (payload + payloadSize + 3, m_RelayTag); + payloadSize += 7; + } payloadSize += CreatePaddingBlock (payload + payloadSize, 64 - payloadSize); // KDF for SessionCreated m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) @@ -810,6 +819,8 @@ namespace transport Terminate (); break; case eSSU2BlkRelayRequest: + LogPrint (eLogDebug, "SSU2: RelayRequest"); + HandleRelayRequest (buf + offset, size); break; case eSSU2BlkRelayResponse: break; @@ -833,6 +844,13 @@ namespace transport case eSSU2BlkIntroKey: break; case eSSU2BlkRelayTagRequest: + LogPrint (eLogDebug, "SSU2: RelayTagRequest"); + HandleRelayRequest (buf + offset, size); + if (!m_RelayTag) + { + RAND_bytes ((uint8_t *)&m_RelayTag, 4); + m_Server.AddRelay (m_RelayTag, shared_from_this ()); + } break; case eSSU2BlkRelayTag: break; @@ -991,6 +1009,36 @@ namespace transport break; return isLast; } + + void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) + { + // we are Bob + uint32_t relayTag = bufbe32toh (buf + 5); // relay tag + auto session = m_Server.FindRelaySession (relayTag); + if (!session) + { + LogPrint (eLogWarning, "SSU2: Session with relay tag ", relayTag, " not found"); + return; // TODO: send relay response + } + SignedData s; + s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue + s.Insert (i2p::context.GetIdentHash (), 32); // bhash + s.Insert (session->GetRemoteIdentity ()->GetIdentHash (), 32); // chash + s.Insert (buf + 1, 14); // nonce, relay tag, timestamp, ver, asz + uint8_t asz = buf[14]; + s.Insert (buf + 15, asz + 2); // Alice IP, Alice Port + if (!s.Verify (GetRemoteIdentity (), buf + 17 + asz)) + { + LogPrint (eLogWarning, "SSU2: RelayRequest signature verification failed"); + return; // TODO: send relay response + } + + // send relay intro to Charlie + uint8_t payload[SSU2_MTU]; + size_t payloadSize = CreateRelayIntroBlock (payload, SSU2_MTU, buf + 1, len -1); + payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MTU - payloadSize); + session->SendData (payload, payloadSize); + } bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep) { @@ -1155,6 +1203,18 @@ namespace transport msg->offset += msgLen; return msgLen + 8; } + + size_t SSU2Session::CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen) + { + buf[0] = eSSU2BlkRelayIntro; + size_t payloadSize = 1/* flag */ + 32/* Alice router hash */ + introDataLen; + if (payloadSize + 3 > len) return 0; + htobe16buf (buf + 1, payloadSize); // size + buf[3] = 0; // flag + memcpy (buf + 4, GetRemoteIdentity ()->GetIdentHash (), 32); // Alice router hash + memcpy (buf + 36, introData, introDataLen); + return payloadSize + 3; + } std::shared_ptr SSU2Session::ExtractRouterInfo (const uint8_t * buf, size_t size) { @@ -1441,6 +1501,29 @@ namespace transport if (session) m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session); } + + void SSU2Server::AddRelay (uint32_t tag, std::shared_ptr relay) + { + m_Relays.emplace (tag, relay); + } + + void SSU2Server::RemoveRelay (uint32_t tag) + { + m_Relays.erase (tag); + } + + std::shared_ptr SSU2Server::FindRelaySession (uint32_t tag) + { + auto it = m_Relays.find (tag); + if (it != m_Relays.end ()) + { + if (it->second->IsEstablished ()) + return it->second; + else + m_Relays.erase (it); + } + return nullptr; + } void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index da8adcb3..b70b1ebe 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -185,6 +185,7 @@ namespace transport void HandleFirstFragment (const uint8_t * buf, size_t len); void HandleFollowOnFragment (const uint8_t * buf, size_t len); bool ConcatOutOfSequenceFragments (std::shared_ptr m); // true if message complete + void HandleRelayRequest (const uint8_t * buf, size_t len); size_t CreateAddressBlock (const boost::asio::ip::udp::endpoint& ep, uint8_t * buf, size_t len); size_t CreateAckBlock (uint8_t * buf, size_t len); @@ -192,6 +193,7 @@ namespace transport size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg); size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg); size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID); + size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen); private: @@ -212,6 +214,7 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; bool m_IsDataReceived; size_t m_WindowSize; + uint32_t m_RelayTag; }; class SSU2Server: private i2p::util::RunnableServiceWithWork @@ -246,6 +249,10 @@ namespace transport void RemoveSession (uint64_t connID); void AddPendingOutgoingSession (std::shared_ptr session); + void AddRelay (uint32_t tag, std::shared_ptr relay); + void RemoveRelay (uint32_t tag); + std::shared_ptr FindRelaySession (uint32_t tag); + void Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to); void Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, @@ -281,6 +288,7 @@ namespace transport std::unordered_map > m_Sessions; std::map > m_PendingOutgoingSessions; std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) + std::map > m_Relays; // we are introducer, relay tag -> session i2p::util::MemoryPoolMt m_PacketsPool; boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer; std::shared_ptr m_LastSession;