diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp deleted file mode 100644 index de077e60f2f..00000000000 --- a/libi2pd/BloomFilter.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 "BloomFilter.h" -#include "I2PEndian.h" -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief decaying bloom filter implementation */ - class DecayingBloomFilter : public IBloomFilter - { - public: - - DecayingBloomFilter(const std::size_t size) - { - m_Size = size; - m_Data = new uint8_t[size]; - } - - /** @brief implements IBloomFilter::~IBloomFilter */ - ~DecayingBloomFilter() - { - delete [] m_Data; - } - - /** @brief implements IBloomFilter::Add */ - bool Add(const uint8_t * data, std::size_t len) - { - std::size_t idx; - uint8_t mask; - Get(data, len, idx, mask); - if(m_Data[idx] & mask) return false; // filter hit - m_Data[idx] |= mask; - return true; - } - - /** @brief implements IBloomFilter::Decay */ - void Decay() - { - // reset bloom filter buffer - memset(m_Data, 0, m_Size); - } - - private: - /** @brief get bit index for for data */ - void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm) - { - bm = 1; - uint8_t digest[32]; - // TODO: use blake2 because it's faster - SHA256(data, len, digest); - uint64_t i = buf64toh(digest); - idx = i % m_Size; - bm <<= (i % 8); - } - - uint8_t * m_Data; - std::size_t m_Size; - }; - - - BloomFilterPtr BloomFilter(std::size_t capacity) - { - return std::make_shared(capacity); - } -} -} diff --git a/libi2pd/BloomFilter.h b/libi2pd/BloomFilter.h deleted file mode 100644 index ade854e468b..00000000000 --- a/libi2pd/BloomFilter.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (c) 2013-2020, 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 -*/ - -#ifndef BLOOM_FILTER_H_ -#define BLOOM_FILTER_H_ -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief interface for bloom filter */ - struct IBloomFilter - { - - /** @brief destructor */ - virtual ~IBloomFilter() {}; - /** @brief add entry to bloom filter, return false if filter hit otherwise return true */ - virtual bool Add(const uint8_t * data, std::size_t len) = 0; - /** @brief optionally decay old entries */ - virtual void Decay() = 0; - }; - - typedef std::shared_ptr BloomFilterPtr; - - /** @brief create bloom filter */ - BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8); - -} -} - -#endif diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index d7fb965efbe..b152cdd056a 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -239,55 +239,6 @@ namespace crypto static BIGNUM * (* g_ElggTable)[255] = nullptr; -// DH - - DHKeys::DHKeys () - { - m_DH = DH_new (); - DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); - DH_set0_key (m_DH, NULL, NULL); - } - - DHKeys::~DHKeys () - { - DH_free (m_DH); - } - - void DHKeys::GenerateKeys () - { - BIGNUM * priv_key = NULL, * pub_key = NULL; -#if !defined(__x86_64__) // use short exponent for non x64 - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); -#endif - if (g_ElggTable) - { -#if defined(__x86_64__) - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); -#endif - auto ctx = BN_CTX_new (); - pub_key = ElggPow (priv_key, g_ElggTable, ctx); - DH_set0_key (m_DH, pub_key, priv_key); - BN_CTX_free (ctx); - } - else - { - DH_set0_key (m_DH, NULL, priv_key); - DH_generate_key (m_DH); - DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key); - } - - bn2buf (pub_key, m_PublicKey, 256); - } - - void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) - { - BIGNUM * pk = BN_bin2bn (pub, 256, NULL); - DH_compute_key (shared, pk, m_DH); - BN_free (pk); - } - // x25519 X25519Keys::X25519Keys () { @@ -601,77 +552,6 @@ namespace crypto BN_CTX_free (ctx); } -// HMAC - const uint64_t IPAD = 0x3636363636363636; - const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; - - - static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; - static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; - - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) - // key is 32 bytes - // digest is 16 bytes - // block size is 64 bytes - { - uint64_t buf[256]; - uint64_t hash[12]; // 96 bytes -#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) - if(i2p::cpu::avx) - { - __asm__ - ( - "vmovups %[key], %%ymm0 \n" - "vmovups %[ipad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[buf]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[buf]) \n" - "vmovups %[opad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[hash]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[hash]) \n" - "vzeroall \n" // end of AVX - "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes - : - : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), - [buf]"r"(buf), [hash]"r"(hash) - : "memory", "%xmm0" // TODO: change to %ymm0 later - ); - } - else -#endif - { - // ikeypad - buf[0] = key.GetLL ()[0] ^ IPAD; - buf[1] = key.GetLL ()[1] ^ IPAD; - buf[2] = key.GetLL ()[2] ^ IPAD; - buf[3] = key.GetLL ()[3] ^ IPAD; - buf[4] = IPAD; - buf[5] = IPAD; - buf[6] = IPAD; - buf[7] = IPAD; - // okeypad - hash[0] = key.GetLL ()[0] ^ OPAD; - hash[1] = key.GetLL ()[1] ^ OPAD; - hash[2] = key.GetLL ()[2] ^ OPAD; - hash[3] = key.GetLL ()[3] ^ OPAD; - hash[4] = OPAD; - hash[5] = OPAD; - hash[6] = OPAD; - hash[7] = OPAD; - // fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (hash + 10, 0, 16); - } - - // concatenate with msg - memcpy (buf + 8, msg, len); - // calculate first hash - MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes - - // calculate digest - MD5((uint8_t *)hash, 96, digest); - } - // AES #ifdef __AES__ #define KeyExpansion256(round0,round1) \ diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index c6dcd2ccc69..d4cbda97a49 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -62,24 +62,6 @@ namespace crypto // RSA const BIGNUM * GetRSAE (); - // DH - class DHKeys - { - public: - - DHKeys (); - ~DHKeys (); - - void GenerateKeys (); - const uint8_t * GetPublicKey () const { return m_PublicKey; }; - void Agree (const uint8_t * pub, uint8_t * shared); - - private: - - DH * m_DH; - uint8_t m_PublicKey[256]; - }; - // x25519 class X25519Keys { @@ -121,10 +103,6 @@ namespace crypto bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); - // HMAC - typedef i2p::data::Tag<32> MACKey; - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); - // AES struct ChipherBlock { diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp deleted file mode 100644 index eec558572ac..00000000000 --- a/libi2pd/SSU.cpp +++ /dev/null @@ -1,996 +0,0 @@ -/* -* 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 "RouterContext.h" -#include "NetDb.hpp" -#include "Config.h" -#include "util.h" -#include "SSU.h" - -#if defined(__linux__) && !defined(_NETINET_IN_H) - #include -#endif - -#ifdef _WIN32 -#include -#endif - -namespace i2p -{ -namespace transport -{ - SSUServer::SSUServer (int port): - m_IsRunning(false), m_Thread (nullptr), - m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), - m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), - m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), - m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), - m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service), - m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service), - m_IsSyncClockFromPeers (true) - { - } - - SSUServer::~SSUServer () - { - } - - void SSUServer::OpenSocket () - { - try - { - m_Socket.open (boost::asio::ip::udp::v4()); - m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_Socket.bind (m_Endpoint); - LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: Failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::OpenSocketV6 () - { - try - { - m_SocketV6.open (boost::asio::ip::udp::v6()); - m_SocketV6.set_option (boost::asio::ip::v6_only (true)); - m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); -#if defined(__linux__) && !defined(_NETINET_IN_H) - if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address - { - // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others -#if (BOOST_VERSION >= 105500) - typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; -#else - typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; -#endif - m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); - } -#endif - m_SocketV6.bind (m_EndpointV6); - LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: Failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::Start () - { - i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); - if (context.SupportsV4 ()) - { - OpenSocket (); - m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); - m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); - ScheduleTermination (); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers - } - if (context.SupportsV6 ()) - { - OpenSocketV6 (); - m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); - ScheduleTerminationV6 (); - ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers - } - SchedulePeerTestsCleanupTimer (); - } - - void SSUServer::Stop () - { - DeleteAllSessions (); - m_IsRunning = false; - m_TerminationTimer.cancel (); - m_TerminationTimerV6.cancel (); - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimerV6.cancel (); - m_Service.stop (); - m_Socket.close (); - m_SocketV6.close (); - m_ReceiversService.stop (); - m_ReceiversServiceV6.stop (); - if (m_ReceiversThread) - { - m_ReceiversThread->join (); - delete m_ReceiversThread; - m_ReceiversThread = nullptr; - } - if (m_ReceiversThreadV6) - { - m_ReceiversThreadV6->join (); - delete m_ReceiversThreadV6; - m_ReceiversThreadV6 = nullptr; - } - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - - void SSUServer::Run () - { - i2p::util::SetThreadName("SSU"); - - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: Server runtime exception: ", ex.what ()); - } - } - } - - void SSUServer::RunReceivers () - { - i2p::util::SetThreadName("SSUv4"); - - while (m_IsRunning) - { - try - { - m_ReceiversService.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: Receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - // restart socket - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - } - - void SSUServer::RunReceiversV6 () - { - i2p::util::SetThreadName("SSUv6"); - - while (m_IsRunning) - { - try - { - m_ReceiversServiceV6.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - } - - void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress) - { - if (localAddress.is_v6 ()) - m_EndpointV6.address (localAddress); - else if (localAddress.is_v4 ()) - m_Endpoint.address (localAddress); - } - - void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) - { - m_Relays.emplace (tag, relay); - } - - void SSUServer::RemoveRelay (uint32_t tag) - { - m_Relays.erase (tag); - } - - std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) - { - auto it = m_Relays.find (tag); - if (it != m_Relays.end ()) - { - if (it->second->GetState () == eSessionStateEstablished) - return it->second; - else - m_Relays.erase (it); - } - return nullptr; - } - - void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) - { - boost::system::error_code ec; - if (to.protocol () == boost::asio::ip::udp::v4()) - m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec); - else - m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec); - - if (ec) - { - LogPrint (eLogError, "SSU: Send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")"); - } - } - - void SSUServer::Receive () - { - SSUPacket * packet = m_PacketsPool.AcquireMt (); - m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, - std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::ReceiveV6 () - { - SSUPacket * packet = m_PacketsPool.AcquireMt (); - m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, - std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode - || ecode == boost::asio::error::connection_refused - || ecode == boost::asio::error::connection_reset - || ecode == boost::asio::error::network_unreachable - || ecode == boost::asio::error::host_unreachable -#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO - || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ - || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ - || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ -#endif - ) - // just try continue reading when received ICMP response otherwise socket can crash, - // but better to find out which host were sent it and mark that router as unreachable - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_Socket.available(ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = m_PacketsPool.AcquireMt (); - packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_Socket.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); - m_PacketsPool.ReleaseMt (packet); - break; - } - } - } - - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); - Receive (); - } - else - { - m_PacketsPool.ReleaseMt (packet); - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: Receive error: code ", ecode.value(), ": ", ecode.message ()); - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - - void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode - || ecode == boost::asio::error::connection_refused - || ecode == boost::asio::error::connection_reset - || ecode == boost::asio::error::network_unreachable - || ecode == boost::asio::error::host_unreachable -#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO - || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ - || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ - || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ -#endif - ) - // just try continue reading when received ICMP response otherwise socket can crash, - // but better to find out which host were sent it and mark that router as unreachable - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_SocketV6.available (ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = m_PacketsPool.AcquireMt (); - packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_SocketV6.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); - m_PacketsPool.ReleaseMt (packet);; - break; - } - } - } - - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); - ReceiveV6 (); - } - else - { - m_PacketsPool.ReleaseMt (packet); - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - - void SSUServer::HandleReceivedPackets (std::vector packets, - std::map > * sessions) - { - if (!m_IsRunning) return; - std::shared_ptr session; - for (auto& packet: packets) - { - try - { - if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous - { - if (session) - { - session->FlushData (); - session = nullptr; - } - auto it = sessions->find (packet->from); - if (it != sessions->end ()) - session = it->second; - if (!session && packet->len > 0) - { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); - (*sessions)[packet->from] = session; - LogPrint (eLogDebug, "SSU: New session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); - } - } - if (session) - session->ProcessNextMessage (packet->buf, packet->len, packet->from); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); - if (session) session->FlushData (); - session = nullptr; - } - } - m_PacketsPool.ReleaseMt (packets); - if (session) session->FlushData (); - } - - std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const - { - auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (e); - if (it != sessions.end ()) - return it->second; - else - return nullptr; - } - - bool SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) - { - auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); - if (address) - return CreateSession (router, address, peerTest); - else - LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); - return false; - } - - bool SSUServer::CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest) - { - if (router && address) - { - if (address->UsesIntroducer ()) - m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread - else - { - if (address->host.is_unspecified () || !address->port) return false; - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); - } - } - else - return false; - return true; - } - - void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) - { - auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (remoteEndpoint); - if (it != sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - } - else - { - // otherwise create new session - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - sessions[remoteEndpoint] = session; - - // connect - LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); - session->Connect (); - } - } - - void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, - std::shared_ptr address, bool peerTest) - { - if (router && address && address->UsesIntroducer ()) - { - if (address->IsV4 () && !i2p::context.SupportsV4 ()) return; - if (address->IsV6 () && !i2p::context.SupportsV6 ()) return; - if (!address->host.is_unspecified () && address->port) - { - // we rarely come here - auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions; - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto it = sessions.find (remoteEndpoint); - // check if session is presented already - if (it != sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - return; - } - } - // create new session - int numIntroducers = address->ssu->introducers.size (); - if (numIntroducers > 0) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::shared_ptr introducerSession; - const i2p::data::RouterInfo::Introducer * introducer = nullptr; - // we might have a session to introducer already - auto offset = rand (); - for (int i = 0; i < numIntroducers; i++) - { - auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]); - if (!intr->iPort) continue; // skip invalid introducer - if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer - boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); - if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4 - { - if (!introducer) introducer = intr; - auto it = m_Sessions.find (ep); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } - } - if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6 - { - if (!introducer) introducer = intr; - auto it = m_SessionsV6.find (ep); - if (it != m_SessionsV6.end ()) - { - introducerSession = it->second; - break; - } - } - } - if (!introducer) - { - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented"); - return; - } - - if (introducerSession) // session found - LogPrint (eLogWarning, "SSU: Session to introducer already exists"); - else // create new - { - LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); - boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); - introducerSession = std::make_shared (*this, introducerEndpoint, router); - if (introducerEndpoint.address ().is_v4 ()) - m_Sessions[introducerEndpoint] = introducerSession; - else if (introducerEndpoint.address ().is_v6 ()) - m_SessionsV6[introducerEndpoint] = introducerSession; - } - if (!address->host.is_unspecified () && address->port) - { - // create session - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - if (address->host.is_v4 ()) - m_Sessions[remoteEndpoint] = session; - else if (address->host.is_v6 ()) - m_SessionsV6[remoteEndpoint] = session; - - // introduce - LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), - "] through introducer ", introducer->iHost, ":", introducer->iPort); - session->WaitForIntroduction (); - if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || - (address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) - { - uint8_t buf[1]; - Send (buf, 0, remoteEndpoint); // send HolePunch - } - } - introducerSession->Introduce (*introducer, router); - } - else - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); - } - } - - void SSUServer::DeleteSession (std::shared_ptr session) - { - if (session) - { - session->Close (); - auto& ep = session->GetRemoteEndpoint (); - if (ep.address ().is_v6 ()) - m_SessionsV6.erase (ep); - else - m_Sessions.erase (ep); - } - } - - void SSUServer::DeleteAllSessions () - { - for (auto& it: m_Sessions) - it.second->Close (); - m_Sessions.clear (); - - for (auto& it: m_SessionsV6) - it.second->Close (); - m_SessionsV6.clear (); - } - - template - std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only - { - std::vector > filteredSessions; - for (const auto& s :m_Sessions) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only - { - return GetRandomV4Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - template - std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only - { - std::vector > filteredSessions; - for (const auto& s :m_SessionsV6) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only - { - return GetRandomV6Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - std::list > SSUServer::FindIntroducers (int maxNumIntroducers, - bool v4, std::set& excluded) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::list > ret; - const auto& sessions = v4 ? m_Sessions : m_SessionsV6; - for (const auto& s : sessions) - { - if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished && - ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) - ret.push_back (s.second); - else if (s.second->GetRemoteIdentity ()) - excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ()); - } - if ((int)ret.size () > maxNumIntroducers) - { - // shink ret randomly - int sz = ret.size () - maxNumIntroducers; - for (int i = 0; i < sz; i++) - { - auto ind = rand () % ret.size (); - auto it = ret.begin (); - std::advance (it, ind); - ret.erase (it); - } - } - return ret; - } - - void SSUServer::RescheduleIntroducersUpdateTimer () - { - m_IntroducersUpdateTimer.cancel (); - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - - void SSUServer::ScheduleIntroducersUpdateTimer () - { - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, true)); - } - - void SSUServer::RescheduleIntroducersUpdateTimerV6 () - { - m_IntroducersUpdateTimerV6.cancel (); - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - - void SSUServer::ScheduleIntroducersUpdateTimerV6 () - { - m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); - m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1, false)); - } - - void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4) - { - if (ecode != boost::asio::error::operation_aborted) - { - // timeout expired - if (v4) - { - if (i2p::context.GetStatus () == eRouterStatusTesting) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimer (); - return; - } - if (i2p::context.GetStatus () != eRouterStatusFirewalled) - { - // we don't need introducers - m_Introducers.clear (); - return; - } - // we are firewalled - if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4 - } - else - { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimerV6 (); - return; - } - if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) - { - // we don't need introducers - m_IntroducersV6.clear (); - return; - } - // we are firewalled - auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address (); - if (addr && addr->ssu && addr->ssu->introducers.empty ()) - i2p::context.SetUnreachable (false, true); // v6 - } - - std::list newList; - size_t numIntroducers = 0; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::set excluded; - auto& introducers = v4 ? m_Introducers : m_IntroducersV6; - for (const auto& it : introducers) - { - auto session = FindSession (it); - if (session) - { - if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION) - session->SendKeepAlive (); - if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) - { - newList.push_back (it); - numIntroducers++; - if (session->GetRemoteIdentity ()) - excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ()); - } - else - session = nullptr; - } - if (!session) - i2p::context.RemoveIntroducer (it); - } - if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) - { - // create new - auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates - if (sessions.empty () && !introducers.empty ()) - { - // bump creation time for previous introducers if no new sessions found - LogPrint (eLogDebug, "SSU: No new introducers found. Trying to reuse existing"); - for (const auto& it : introducers) - { - auto session = FindSession (it); - if (session) - session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION); - } - // try again - excluded.clear (); - sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); - } - for (const auto& it1: sessions) - { - const auto& ep = it1->GetRemoteEndpoint (); - i2p::data::RouterInfo::Introducer introducer; - introducer.iHost = ep.address (); - introducer.iPort = ep.port (); - introducer.iTag = it1->GetRelayTag (); - introducer.iKey = it1->GetIntroKey (); - introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION; - if (i2p::context.AddIntroducer (introducer)) - { - newList.push_back (ep); - if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; - } - if (it1->GetRemoteIdentity ()) - excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ()); - } - } - introducers = newList; - if (introducers.size () < SSU_MAX_NUM_INTRODUCERS) - { - for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++) - { - auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded); - if (introducer) - { - auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address (); - if (address && !address->host.is_unspecified () && address->port) - { - boost::asio::ip::udp::endpoint ep (address->host, address->port); - if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet - { - CreateDirectSession (introducer, ep, false); - excluded.insert (introducer->GetIdentHash ()); - } - } - } - else - { - LogPrint (eLogDebug, "SSU: Can't find more introducers"); - break; - } - } - } - if (v4) - ScheduleIntroducersUpdateTimer (); - else - ScheduleIntroducersUpdateTimerV6 (); - } - } - - void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) - { - m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; - } - - PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.role; - else - return ePeerTestParticipantUnknown; - } - - std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.session; - else - return nullptr; - } - - void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - it->second.role = role; - } - - void SSUServer::RemovePeerTest (uint32_t nonce) - { - m_PeerTests.erase (nonce); - } - - void SSUServer::SchedulePeerTestsCleanupTimer () - { - m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); - m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - int numDeleted = 0; - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) - { - if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) - { - numDeleted++; - it = m_PeerTests.erase (it); - } - else - ++it; - } - if (numDeleted > 0) - LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); - // some cleaups. TODO: use separate timer - m_FragmentsPool.CleanUp (); - m_IncompleteMessagesPool.CleanUp (); - m_SentMessagesPool.CleanUp (); - - SchedulePeerTestsCleanupTimer (); - } - } - - void SSUServer::ScheduleTermination () - { - uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout)); - m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_Sessions) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted"); - m_Service.post ([session] - { - LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - else - it.second->CleanUp (ts); - ScheduleTermination (); - } - } - - void SSUServer::ScheduleTerminationV6 () - { - uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; - m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout)); - m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_SessionsV6) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); - m_Service.post ([session] - { - LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - else - it.second->CleanUp (ts); - ScheduleTerminationV6 (); - } - } -} -} diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h deleted file mode 100644 index 25ce4d40776..00000000000 --- a/libi2pd/SSU.h +++ /dev/null @@ -1,159 +0,0 @@ -/* -* 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 -*/ - -#ifndef SSU_H__ -#define SSU_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Crypto.h" -#include "util.h" -#include "I2PEndian.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "I2NPProtocol.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds - const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds - const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour - const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes - const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds - const size_t SSU_MAX_NUM_INTRODUCERS = 3; - const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K - const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K - - struct SSUPacket - { - i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size - boost::asio::ip::udp::endpoint from; - size_t len; - }; - - class SSUServer - { - public: - - SSUServer (int port); - ~SSUServer (); - void Start (); - void Stop (); - bool CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); - bool CreateSession (std::shared_ptr router, - std::shared_ptr address, bool peerTest = false); - void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); - std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; - std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); - std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); - void DeleteSession (std::shared_ptr session); - void DeleteAllSessions (); - - boost::asio::io_service& GetService () { return m_Service; }; - i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; - i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; - i2p::util::MemoryPool& GetSentMessagesPool () { return m_SentMessagesPool; }; - - uint16_t GetPort () const { return m_Endpoint.port (); }; - bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; - void SetLocalAddress (const boost::asio::ip::address& localAddress); - - void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); - void AddRelay (uint32_t tag, std::shared_ptr relay); - void RemoveRelay (uint32_t tag); - std::shared_ptr FindRelaySession (uint32_t tag); - void RescheduleIntroducersUpdateTimer (); - void RescheduleIntroducersUpdateTimerV6 (); - - void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); - PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); - std::shared_ptr GetPeerTestSession (uint32_t nonce); - void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); - void RemovePeerTest (uint32_t nonce); - - private: - - void OpenSocket (); - void OpenSocketV6 (); - void Run (); - void RunReceivers (); - void RunReceiversV6 (); - void Receive (); - void ReceiveV6 (); - void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedPackets (std::vector packets, - std::map >* sessions); - - void CreateSessionThroughIntroducer (std::shared_ptr router, - std::shared_ptr address, bool peerTest = false); - template - std::shared_ptr GetRandomV4Session (Filter filter); - template - std::shared_ptr GetRandomV6Session (Filter filter); - - std::list > FindIntroducers (int maxNumIntroducers, bool v4, std::set& excluded); - void ScheduleIntroducersUpdateTimer (); - void ScheduleIntroducersUpdateTimerV6 (); - void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4); - - void SchedulePeerTestsCleanupTimer (); - void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleTerminationV6 (); - void HandleTerminationTimerV6 (const boost::system::error_code& ecode); - - private: - - struct PeerTest - { - uint64_t creationTime; - PeerTestParticipant role; - std::shared_ptr session; // for Bob to Alice - }; - - volatile bool m_IsRunning; - std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; - boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; - boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; - boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; - boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6, - m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; - bool m_IsSyncClockFromPeers; - std::list m_Introducers, m_IntroducersV6; // introducers we are connected to - std::map > m_Sessions, m_SessionsV6; - std::map > m_Relays; // we are introducer - std::map m_PeerTests; // nonce -> creation time in milliseconds - - i2p::util::MemoryPool m_FragmentsPool; - i2p::util::MemoryPool m_IncompleteMessagesPool; - i2p::util::MemoryPool m_SentMessagesPool; - i2p::util::MemoryPoolMt m_PacketsPool; - - public: - // for HTTP only - const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; - const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; - }; -} -} - -#endif diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp deleted file mode 100644 index 6365381e5c7..00000000000 --- a/libi2pd/SSUData.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/* -* 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; - } - } - } -} -} diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h deleted file mode 100644 index eba0fc287dd..00000000000 --- a/libi2pd/SSUData.h +++ /dev/null @@ -1,131 +0,0 @@ -/* -* 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 -*/ - -#ifndef SSU_DATA_H__ -#define SSU_DATA_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "I2NPProtocol.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "TransportSession.h" - -namespace i2p -{ -namespace transport -{ - const size_t SSU_MTU_V4 = 1484; - const size_t SSU_MTU_V6 = 1488; - const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 - const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 - const int RESEND_INTERVAL = 3; // in seconds - const int MAX_NUM_RESENDS = 5; - const int DECAY_INTERVAL = 20; // in seconds - const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds - const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds - const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check - const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store - // data flags - const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; - const uint8_t DATA_FLAG_WANT_REPLY = 0x04; - const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; - const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; - const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; - const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; - - struct Fragment - { - int fragmentNum; - size_t len; - bool isLast; - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest - - Fragment () = default; - Fragment (int n, const uint8_t * b, int l, bool last): - fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; - }; - - struct FragmentCmp - { - bool operator() (const std::shared_ptr& f1, const std::shared_ptr& f2) const - { - return f1->fragmentNum < f2->fragmentNum; - }; - }; - - struct IncompleteMessage - { - std::shared_ptr msg; - int nextFragmentNum; - uint32_t lastFragmentInsertTime; // in seconds - uint64_t receivedFragmentsBits; - std::set, FragmentCmp> savedFragments; - - IncompleteMessage (std::shared_ptr&& m): msg (m), nextFragmentNum (0), - lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; - void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); - }; - - struct SentMessage - { - std::vector > fragments; - uint32_t nextResendTime; // in seconds - int numResends; - }; - - class SSUSession; - class SSUData - { - public: - - SSUData (SSUSession& session); - ~SSUData (); - - void Start (); - void Stop (); - void CleanUp (uint64_t ts); - - void ProcessMessage (uint8_t * buf, size_t len); - void FlushReceivedMessage (); - void Send (std::shared_ptr msg); - - void AdjustPacketSize (std::shared_ptr remoteRouter); - void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); - - private: - - void SendMsgAck (uint32_t msgID); - void SendFragmentAck (uint32_t msgID, uint64_t bits); - void ProcessAcks (uint8_t *& buf, uint8_t flag); - void ProcessFragments (uint8_t * buf); - void ProcessSentMessageAck (uint32_t msgID); - - void ScheduleResend (); - void HandleResendTimer (const boost::system::error_code& ecode); - - private: - - SSUSession& m_Session; - std::map > m_IncompleteMessages; - std::map > m_SentMessages; - std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds - boost::asio::deadline_timer m_ResendTimer; - int m_MaxPacketSize, m_PacketSize; - i2p::I2NPMessagesHandler m_Handler; - uint32_t m_LastMessageReceivedTime; // in second - }; -} -} - -#endif diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp deleted file mode 100644 index 73f9cdb1711..00000000000 --- a/libi2pd/SSUSession.cpp +++ /dev/null @@ -1,1318 +0,0 @@ -/* -* 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 "version.h" -#include "Crypto.h" -#include "Log.h" -#include "Timestamp.h" -#include "RouterContext.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "SSU.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router, bool peerTest ): - TransportSession (router, SSU_TERMINATION_TIMEOUT), - m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), - m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), - m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) - { - if (router) - { - // we are client - auto address = IsV6 () ? router->GetSSUV6Address () : router->GetSSUAddress (true); - if (address) m_IntroKey = address->i; - m_Data.AdjustPacketSize (router); // mtu - } - else - { - // we are server - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (address) m_IntroKey = address->i; - } - } - - SSUSession::~SSUSession () - { - } - - boost::asio::io_service& SSUSession::GetService () - { - return m_Server.GetService (); - } - - void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) - { - uint8_t sharedKey[256]; - m_DHKeysPair->Agree (pubKey, sharedKey); - - uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; - if (sharedKey[0] & 0x80) - { - sessionKey[0] = 0; - memcpy (sessionKey + 1, sharedKey, 31); - memcpy (macKey, sharedKey + 31, 32); - } - else if (sharedKey[0]) - { - memcpy (sessionKey, sharedKey, 32); - memcpy (macKey, sharedKey + 32, 32); - } - else - { - // find first non-zero byte - uint8_t * nonZero = sharedKey + 1; - while (!*nonZero) - { - nonZero++; - if (nonZero - sharedKey > 32) - { - LogPrint (eLogWarning, "SSU: First 32 bytes of shared key is all zeros. Ignored"); - return; - } - } - - memcpy (sessionKey, nonZero, 32); - SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); - } - m_IsSessionKey = true; - m_SessionKeyEncryption.SetKey (m_SessionKey); - m_SessionKeyDecryption.SetKey (m_SessionKey); - } - - void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - m_NumReceivedBytes += len; - i2p::transport::transports.UpdateReceivedBytes (len); - if (m_State == eSessionStateIntroduced) - { - // HolePunch received - LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); - m_State = eSessionStateUnknown; - Connect (); - } - else - { - if (!len) return; // ignore zero-length packets - if (m_State == eSessionStateEstablished) - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - - if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first - DecryptSessionKey (buf, len); - else - { - if (m_State == eSessionStateEstablished) Reset (); // new session key required - // try intro key depending on side - if (Validate (buf, len, m_IntroKey)) - Decrypt (buf, len, m_IntroKey); - else - { - // try own intro key - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - if (Validate (buf, len, address->i)) - Decrypt (buf, len, address->i); - else - { - LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); - m_Server.DeleteSession (shared_from_this ()); - return; - } - } - } - // successfully decrypted - ProcessMessage (buf, len, senderEndpoint); - } - } - - size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const - { - size_t s = sizeof (SSUHeader); - if (((const SSUHeader *)buf)->IsExtendedOptions ()) - s += buf[s] + 1; // byte right after header is extended options length - return s; - } - - void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - len -= (len & 0x0F); // %16, delete extra padding - if (len <= sizeof (SSUHeader)) return; // drop empty message - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU: SSU header size ", headerSize, " exceeds packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - switch (header->GetPayloadType ()) - { - case PAYLOAD_TYPE_DATA: - ProcessData (buf + headerSize, len - headerSize); - break; - case PAYLOAD_TYPE_SESSION_REQUEST: - ProcessSessionRequest (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CREATED: - ProcessSessionCreated (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CONFIRMED: - ProcessSessionConfirmed (buf, len); // buf with header - break; - case PAYLOAD_TYPE_PEER_TEST: - LogPrint (eLogDebug, "SSU: Peer test received"); - ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_SESSION_DESTROYED: - { - LogPrint (eLogDebug, "SSU: Session destroy received"); - m_Server.DeleteSession (shared_from_this ()); - break; - } - case PAYLOAD_TYPE_RELAY_RESPONSE: - ProcessRelayResponse (buf + headerSize, len - headerSize); - if (m_State != eSessionStateEstablished) - m_Server.DeleteSession (shared_from_this ()); - break; - case PAYLOAD_TYPE_RELAY_REQUEST: - LogPrint (eLogDebug, "SSU: Relay request received"); - ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_RELAY_INTRO: - LogPrint (eLogDebug, "SSU: Relay intro received"); - ProcessRelayIntro (buf + headerSize, len - headerSize); - break; - default: - LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); - } - } - - void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: Session request"); - bool sendRelayTag = true; - auto headerSize = sizeof (SSUHeader); - if (((SSUHeader *)buf)->IsExtendedOptions ()) - { - uint8_t extendedOptionsLen = buf[headerSize]; - headerSize++; - if (extendedOptionsLen >= 2) // options are presented - { - uint16_t flags = bufbe16toh (buf + headerSize); - sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; - } - headerSize += extendedOptionsLen; - } - if (headerSize >= len) - { - LogPrint (eLogError, "SSU message: Session request header size ", headerSize, " exceeds packet length ", len); - return; - } - if (!m_DHKeysPair) - { - auto pair = std::make_shared (); - pair->GenerateKeys (); - m_DHKeysPair = pair; - } - CreateAESandMacKey (buf + headerSize); - SendSessionCreated (buf + headerSize, sendRelayTag); - } - - void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) - { - if (!IsOutgoing () || !m_DHKeysPair) - { - LogPrint (eLogWarning, "SSU: Unsolicited session created message"); - return; - } - - LogPrint (eLogDebug, "SSU message: session created"); - m_ConnectTimer.cancel (); // connect timer - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU message: Session created header size ", headerSize, " exceeds packet length ", len); - return; - } - uint8_t * payload = buf + headerSize; - uint8_t * y = payload; - CreateAESandMacKey (y); - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - payload += 256; - boost::asio::ip::address ourIP; - uint16_t ourPort = 0; - auto addressAndPortLen = ExtractIPAddressAndPort (payload, len, ourIP, ourPort); - if (!addressAndPortLen) return; - uint8_t * ourAddressAndPort = payload + 1; - payload += addressAndPortLen; - addressAndPortLen--; // -1 byte address size - s.Insert (ourAddressAndPort, addressAndPortLen); // address + port - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (payload, 8); // relayTag and signed on time - m_RelayTag = bufbe32toh (payload); - payload += 4; // relayTag - uint32_t signedOnTime = bufbe32toh(payload); - payload += 4; // signed on time - // decrypt signature - size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) signatureLen += (16 - paddingSize); - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); - m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload - // verify signature - if (s.Verify (m_RemoteIdentity, payload)) - { - if (ourIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - int offset = (int)ts - signedOnTime; - if (m_Server.IsSyncClockFromPeers ()) - { - if (std::abs (offset) > SSU_CLOCK_THRESHOLD) - { - LogPrint (eLogWarning, "SSU: Clock adjusted by ", -offset, " seconds"); - i2p::util::AdjustTimeOffset (-offset); - } - } - else if (std::abs (offset) > SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU: Clock skew detected ", offset, ". Check your clock"); - i2p::context.SetError (eRouterErrorClockSkew); - } - } - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - if (!i2p::util::net::IsInReservedRange (ourIP)) - { - i2p::context.UpdateAddress (ourIP); - SendSessionConfirmed (y, ourAddressAndPort, addressAndPortLen); - } - else - { - LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); - Failed (); - } - } - else - { - LogPrint (eLogError, "SSU: Message 'created' signature verification failed"); - Failed (); - } - } - - void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU: Session confirmed received"); - m_ConnectTimer.cancel (); - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU: Session confirmed header size ", headerSize, " exceeds packet length ", len); - return; - } - const uint8_t * payload = buf + headerSize; - payload++; // identity fragment info - uint16_t identitySize = bufbe16toh (payload); - if (identitySize + headerSize + 7 > len) // 7 = fragment info + fragment size + signed on time - { - LogPrint (eLogError, "SSU: Session confirmed identity size ", identitySize, " exceeds packet length ", len); - return; - } - payload += 2; // size of identity fragment - auto identity = std::make_shared (payload, identitySize); - auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); - m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); - payload += identitySize; // identity - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t signedOnTime = bufbe32toh(payload); - if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU: Message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); - Failed (); - return; - } - if (m_SignedData) - m_SignedData->Insert (payload, 4); // insert Alice's signed on time - payload += 4; // signed-on time - size_t fullSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = fullSize & 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - payload += paddingSize; - if (fullSize + paddingSize > len) - { - LogPrint (eLogError, "SSU: Session confirmed message is too short ", len); - return; - } - // verify signature - if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) - { - m_Data.Send (CreateDeliveryStatusMsg (0)); - Established (); - } - else - { - LogPrint (eLogError, "SSU: Message 'confirmed' signature verification failed"); - Failed (); - } - } - - void SSUSession::SendSessionRequest () - { - uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - uint8_t flag = 0; - // fill extended options, 3 bytes extended options don't change message size - bool isV4 = m_RemoteEndpoint.address ().is_v4 (); - if ((isV4 && i2p::context.GetStatus () == eRouterStatusOK) || - (!isV4 && i2p::context.GetStatusV6 () == eRouterStatusOK)) // we don't need relays - { - // tell out peer to now assign relay tag - flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length - uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG - htobe16buf (payload, flags); - payload += 2; - } - // fill payload - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x - if (isV4) - { - payload[256] = 4; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - } - else - { - payload[256] = 16; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - } - // encrypt and send - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); - m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); - } - - void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) - { - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - - uint8_t buf[96 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, introducer.iTag); - payload += 4; - *payload = 0; // no address - payload++; - htobuf16(payload, 0); // port = 0 - payload += 2; - *payload = 0; // challenge - payload++; - memcpy (payload, (const uint8_t *)address->i, 32); - payload += 32; - htobe32buf (payload, nonce); // nonce - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - if (m_State == eSessionStateEstablished) - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); - else - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); - m_Server.Send (buf, 96, m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU: Relay request sent"); - } - - void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) - { - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported"); - return; - } - SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time - s.Insert (x, 256); // x - - uint8_t buf[384 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); - s.Insert (payload, 256); // y - payload += 256; - if (m_RemoteEndpoint.address ().is_v4 ()) - { - // ipv4 - *payload = 4; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - s.Insert (payload, 4); // remote endpoint IP V4 - payload += 4; - } - else - { - // ipv6 - *payload = 16; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - s.Insert (payload, 16); // remote endpoint IP V6 - payload += 16; - } - htobe16buf (payload, m_RemoteEndpoint.port ()); - s.Insert (payload, 2); // remote port - payload += 2; - if (address->host.is_v4 ()) - s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 - else - s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 - s.Insert (htobe16 (address->port)); // our port - if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer (!IsV6 ())) - { - RAND_bytes((uint8_t *)&m_SentRelayTag, 4); - if (!m_SentRelayTag) m_SentRelayTag = 1; - } - htobe32buf (payload, m_SentRelayTag); - payload += 4; // relay tag - htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time - payload += 4; - s.Insert (payload - 8, 4); // relayTag - // we have to store this signed data for session confirmed - // same data but signed on time, it will Alice's there - m_SignedData = std::unique_ptr(new SignedData (s)); - s.Insert (payload - 4, 4); // BOB's signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt signature and padding with newly created session key - size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) - { - // fill random padding - RAND_bytes(payload + signatureLen, (16 - paddingSize)); - signatureLen += (16 - paddingSize); - } - m_SessionKeyEncryption.SetIV (iv); - m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); - payload += signatureLen; - size_t msgLen = payload - buf; - - // encrypt message with intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); - Send (buf, msgLen); - } - - void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) - { - uint8_t buf[512 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 1; // 1 fragment - payload++; // info - size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes - htobe16buf (payload, identLen); - payload += 2; // cursize - i2p::context.GetIdentity ()->ToBuffer (payload, identLen); - payload += identLen; - uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); - htobe32buf (payload, signedOnTime); // signed on time - payload += 4; - auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = ((payload - buf) + signatureLen)%16; - if (paddingSize > 0) paddingSize = 16 - paddingSize; - RAND_bytes(payload, paddingSize); // fill padding with random - payload += paddingSize; // padding size - // signature - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (htobe32 (m_RelayTag)); // relay tag - s.Insert (htobe32 (signedOnTime)); // signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - payload += signatureLen; - - size_t msgLen = payload - buf; - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); - Send (buf, msgLen); - } - - void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) - { - uint32_t relayTag = bufbe32toh (buf); - auto session = m_Server.FindRelaySession (relayTag); - if (session) - { - buf += 4; // relay tag - uint8_t size = *buf; - buf++; // size - buf += size; // address - buf += 2; // port - uint8_t challengeSize = *buf; - buf++; // challenge size - buf += challengeSize; - const uint8_t * introKey = buf; - buf += 32; // introkey - uint32_t nonce = bufbe32toh (buf); - SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); - SendRelayIntro (session, from); - } - } - - void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) - { - bool isV4 = to.address ().is_v4 (); // Charle's - bool isV4A = from.address ().is_v4 (); // Alice's - if ((isV4 && !isV4A) || (!isV4 && isV4A)) - { - LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay response"); - return; - } - uint8_t buf[80 + 18] = {0}; // 64 for ipv4 and 80 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - // Charlie - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, to.address ().to_v4 ().to_bytes ().data (), 4); // Charlie's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Charlie's IP V6 - payload += 16; // address - } - htobe16buf (payload, to.port ()); // Charlie's port - payload += 2; // port - // Alice - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 - payload += 16; // address - } - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - htobe32buf (payload, nonce); - - if (m_State == eSessionStateEstablished) - { - // encrypt with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); - Send (buf, isV4 ? 64 : 80); - } - else - { - // ecrypt with Alice's intro key - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); - m_Server.Send (buf, isV4 ? 64 : 80, from); - } - LogPrint (eLogDebug, "SSU: Relay response sent"); - } - - void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) - { - if (!session) return; - bool isV4 = from.address ().is_v4 (); // Alice's - bool isV4C = session->m_RemoteEndpoint.address ().is_v4 (); // Charlie's - if ((isV4 && !isV4C) || (!isV4 && isV4C)) - { - LogPrint (eLogWarning, "SSU: Charlie's IP and Alice's IP belong to different networks for relay intro"); - return; - } - uint8_t buf[64 + 18] = {0}; // 48 for ipv4 and 64 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 - payload += 16; // address - } - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - *payload = 0; // challenge size - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, isV4 ? 48 : 64, session->m_SessionKey, iv, session->m_MacKey); - m_Server.Send (buf, isV4 ? 48 : 64, session->m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU: Relay intro sent"); - } - - void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: Relay response received"); - boost::asio::ip::address remoteIP; - uint16_t remotePort = 0; - auto remoteSize = ExtractIPAddressAndPort (buf, len, remoteIP, remotePort); - if (!remoteSize) return; - buf += remoteSize; len -= remoteSize; - boost::asio::ip::address ourIP; - uint16_t ourPort = 0; - auto ourSize = ExtractIPAddressAndPort (buf, len, ourIP, ourPort); - if (!ourSize) return; - buf += ourSize; len -= ourSize; - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - if (!i2p::util::net::IsInReservedRange (ourIP)) - i2p::context.UpdateAddress (ourIP); - else - LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); - if (ourIP.is_v4 ()) - { - if (ourPort != m_Server.GetPort ()) - { - if (i2p::context.GetStatus () == eRouterStatusTesting) - i2p::context.SetError (eRouterErrorSymmetricNAT); - } - else if (i2p::context.GetError () == eRouterErrorSymmetricNAT) - i2p::context.SetError (eRouterErrorNone); - } - uint32_t nonce = bufbe32toh (buf); - buf += 4; // nonce - auto it = m_RelayRequests.find (nonce); - if (it != m_RelayRequests.end ()) - { - // check if we are waiting for introduction - boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); - if (!m_Server.FindSession (remoteEndpoint)) - { - // we didn't have correct endpoint when sent relay request - // now we do - LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); - if ((remoteIP.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) || - (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) - m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch - // we assume that HolePunch has been sent by this time and our SessionRequest will go through - m_Server.CreateDirectSession (it->second.first, remoteEndpoint, false); - } - // delete request - m_RelayRequests.erase (it); - // cancel connect timer - m_ConnectTimer.cancel (); - } - else - LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); - } - - void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) - { - boost::asio::ip::address ip; - uint16_t port = 0; - ExtractIPAddressAndPort (buf, len, ip, port); - if (!ip.is_unspecified () && port) - // send hole punch of 0 bytes - m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (ip, port)); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, - const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - memcpy (header->iv, iv, 16); - header->flag = flag | (payloadType << 4); // MSB is 0 - htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCEncryption encryption; - encryption.SetKey (aesKey); - encryption.SetIV (iv); - encryption.Encrypt (encrypted, encryptedLen, encrypted); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) - { - FillHeaderAndEncrypt (payloadType, buf, len, buf); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)out; - RAND_bytes (header->iv, 16); // random iv - m_SessionKeyEncryption.SetIV (header->iv); - SSUHeader * inHeader = (SSUHeader *)in; - inHeader->flag = payloadType << 4; // MSB is 0 - htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; - uint16_t encryptedLen = len - (encrypted - out); - m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); - // assume actual out buffer size is 18 (16 + 2) bytes more - memcpy (out + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); - } - - void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (aesKey); - decryption.SetIV (header->iv); - decryption.Decrypt (encrypted, encryptedLen, encrypted); - } - - void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - if (encryptedLen > 0) - { - m_SessionKeyDecryption.SetIV (header->iv); - m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); - } - } - - bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return false; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - uint8_t digest[16]; - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); - return !memcmp (header->mac, digest, 16); - } - - void SSUSession::Connect () - { - if (m_State == eSessionStateUnknown) - { - ScheduleConnectTimer (); // set connect timer - m_DHKeysPair = std::make_shared (); - m_DHKeysPair->GenerateKeys (); - SendSessionRequest (); - } - } - - void SSUSession::WaitForConnect () - { - if (!IsOutgoing ()) // incoming session - ScheduleConnectTimer (); - else - LogPrint (eLogError, "SSU: Wait for connect for outgoing session"); - } - - void SSUSession::ScheduleConnectTimer () - { - m_ConnectTimer.cancel (); - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); -} - - void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) - { - if (!ecode) - { - // timeout expired - LogPrint (eLogWarning, "SSU: Session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); - Failed (); - } - } - - void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to) - { - if (m_State == eSessionStateUnknown) - { - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - auto ts = i2p::util::GetSecondsSinceEpoch (); - m_RelayRequests.emplace (nonce, std::make_pair (to, ts)); - SendRelayRequest (introducer, nonce); - } - - void SSUSession::WaitForIntroduction () - { - m_State = eSessionStateIntroduced; - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - - void SSUSession::Close () - { - SendSessionDestroyed (); - Reset (); - m_State = eSessionStateClosed; - } - - void SSUSession::Reset () - { - m_State = eSessionStateUnknown; - transports.PeerDisconnected (shared_from_this ()); - m_Data.Stop (); - m_ConnectTimer.cancel (); - if (m_SentRelayTag) - { - m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore - m_SentRelayTag = 0; - } - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_IsSessionKey = false; - } - - void SSUSession::Done () - { - GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); - } - - void SSUSession::Established () - { - m_State = eSessionStateEstablished; - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_Data.Start (); - transports.PeerConnected (shared_from_this ()); - if (m_IsPeerTest) - SendPeerTest (); - if (m_SentRelayTag) - m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - - void SSUSession::Failed () - { - if (m_State != eSessionStateFailed) - { - m_State = eSessionStateFailed; - m_Server.DeleteSession (shared_from_this ()); - } - } - - void SSUSession::SendI2NPMessages (const std::vector >& msgs) - { - GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); - } - - void SSUSession::PostI2NPMessages (std::vector > msgs) - { - if (m_State == eSessionStateEstablished) - { - for (const auto& it: msgs) - if (it) - { - if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) - m_Data.Send (it); - else - LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); - } - } - } - - void SSUSession::ProcessData (uint8_t * buf, size_t len) - { - m_Data.ProcessMessage (buf, len); - m_IsDataReceived = true; - } - - void SSUSession::FlushData () - { - if (m_IsDataReceived) - { - m_Data.FlushReceivedMessage (); - m_IsDataReceived = false; - } - } - - void SSUSession::CleanUp (uint64_t ts) - { - m_Data.CleanUp (ts); - for (auto it = m_RelayRequests.begin (); it != m_RelayRequests.end ();) - { - if (ts > it->second.second + SSU_CONNECT_TIMEOUT) - it = m_RelayRequests.erase (it); - else - ++it; - } - } - - void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - uint32_t nonce = bufbe32toh (buf); // 4 bytes - boost::asio::ip::address addr; // Alice's address - uint16_t port = 0; // and port - auto size = ExtractIPAddressAndPort (buf + 4, len - 4, addr, port); - if (port && (size != 7) && (size != 19)) - { - LogPrint (eLogWarning, "SSU: Address of ", size - 3, " bytes not supported"); - return; - } - const uint8_t * introKey = buf + 4 + size; - switch (m_Server.GetPeerTestParticipant (nonce)) - { - // existing test - case ePeerTestParticipantAlice1: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - { - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); - if (IsV6 ()) - { - if (i2p::context.GetStatusV6 () == eRouterStatusTesting) - { - i2p::context.SetStatusV6 (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimerV6 (); - } - } - else if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK - { - i2p::context.SetStatus (eRouterStatusFirewalled); - m_Server.RescheduleIntroducersUpdateTimer (); - } - } - else - { - LogPrint (eLogDebug, "SSU: First peer test from Charlie. We are Alice"); - if (m_State == eSessionStateEstablished) - LogPrint (eLogWarning, "SSU: First peer test from Charlie through established session. We are Alice"); - if (IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); - else - i2p::context.SetStatus (eRouterStatusOK); - m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie - } - break; - } - case ePeerTestParticipantAlice2: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Alice"); - else - { - // peer test successive - LogPrint (eLogDebug, "SSU: Second peer test from Charlie. We are Alice"); - if (IsV6 ()) - i2p::context.SetStatusV6 (eRouterStatusOK); - else - i2p::context.SetStatus (eRouterStatusOK); - m_Server.RemovePeerTest (nonce); - } - break; - } - case ePeerTestParticipantBob: - { - LogPrint (eLogDebug, "SSU: Peer test from Charlie. We are Bob"); - auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest - if (session && session->m_State == eSessionStateEstablished) - { - const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob - session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice - } - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - case ePeerTestParticipantCharlie: - { - LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Charlie"); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - // test not found - case ePeerTestParticipantUnknown: - { - if (m_State == eSessionStateEstablished) - { - // new test - if (port) - { - LogPrint (eLogDebug, "SSU: Peer test from Bob. We are Charlie"); - Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) - { - m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); - SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob - } - } - else - { - LogPrint (eLogDebug, "SSU: Peer test from Alice. We are Bob"); - auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie - if (session) - { - m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); - session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address - } - } - } - else - LogPrint (eLogError, "SSU: Unexpected peer test"); - } - } - } - - void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, - const uint8_t * introKey, bool toAddress, bool sendAddress) - // toAddress is true for Alice<->Chalie communications only - // sendAddress is false if message comes from Alice - { - uint8_t buf[80 + 18] = {0}; - uint8_t iv[16]; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, nonce); - payload += 4; // nonce - // address and port - if (sendAddress) - { - if (address.is_v4 ()) - { - *payload = 4; - memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 - } - else if (address.is_v6 ()) - { - *payload = 16; - memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 - } - else - *payload = 0; - payload += (payload[0] + 1); - } - else - { - *payload = 0; - payload++; //size - } - htobe16buf (payload, port); - payload += 2; // port - // intro key - if (toAddress) - { - // send our intro key to address instead of its own - auto addr = address.is_v4 () ? i2p::context.GetRouterInfo ().GetSSUAddress (true) : // ipv4 - i2p::context.GetRouterInfo ().GetSSUV6Address (); - if (addr) - memcpy (payload, addr->i, 32); // intro key - else - LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); - } - else - memcpy (payload, introKey, 32); // intro key - - // send - RAND_bytes (iv, 16); // random iv - if (toAddress) - { - // encrypt message with specified intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); - boost::asio::ip::udp::endpoint e (address, port); - m_Server.Send (buf, 80, e); - } - else - { - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); - Send (buf, 80); - } - } - - void SSUSession::SendPeerTest () - { - // we are Alice - LogPrint (eLogDebug, "SSU: Sending peer test"); - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); - if (!address) - { - LogPrint (eLogInfo, "SSU: SSU is not supported. Can't send peer test"); - return; - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - if (!nonce) nonce = 1; - m_IsPeerTest = false; - m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); - SendPeerTest (nonce, boost::asio::ip::address(), 0, address->i, false, false); // address and port always zero for Alice - } - - void SSUSession::SendKeepAlive () - { - if (m_State == eSessionStateEstablished) - { - uint8_t buf[48 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 0; // flags - payload++; - *payload = 0; // num fragments - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); - Send (buf, 48); - LogPrint (eLogDebug, "SSU: keep-alive sent"); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - } - - void SSUSession::SendSessionDestroyed () - { - if (m_IsSessionKey) - { - uint8_t buf[48 + 18] = {0}; - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); - try - { - Send (buf, 48); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "SSU: Exception while sending session destoroyed: ", ex.what ()); - } - LogPrint (eLogDebug, "SSU: Session destroyed sent"); - } - } - - void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) - { - uint8_t buf[SSU_MTU_V4 + 18] = {0}; - size_t msgSize = len + sizeof (SSUHeader); - size_t paddingSize = msgSize & 0x0F; // %16 - if (paddingSize > 0) msgSize += (16 - paddingSize); - if (msgSize > SSU_MTU_V4) - { - LogPrint (eLogWarning, "SSU: Payload size ", msgSize, " exceeds MTU"); - return; - } - memcpy (buf + sizeof (SSUHeader), payload, len); - // encrypt message with session key - FillHeaderAndEncrypt (type, buf, msgSize); - Send (buf, msgSize); - } - - void SSUSession::Send (const uint8_t * buf, size_t size) - { - m_NumSentBytes += size; - i2p::transport::transports.UpdateSentBytes (size); - m_Server.Send (buf, size, m_RemoteEndpoint); - } - - size_t SSUSession::ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port) - { - if (!len) return 0; - uint8_t size = *buf; - size_t s = 1 + size + 2; // size + address + port - if (len < s) - { - LogPrint (eLogWarning, "SSU: Address is too short ", len); - port = 0; - return len; - } - buf++; // size - if (size == 4) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf, 4); - ip = boost::asio::ip::address_v4 (bytes); - } - else if (size == 16) - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), buf, 16); - ip = boost::asio::ip::address_v6 (bytes); - } - else - LogPrint (eLogWarning, "SSU: Address size ", int(size), " is not supported"); - buf += size; - port = bufbe16toh (buf); - return s; - } -} -} diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h deleted file mode 100644 index e28b49913b5..00000000000 --- a/libi2pd/SSUSession.h +++ /dev/null @@ -1,177 +0,0 @@ -/* -* 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 -*/ - -#ifndef SSU_SESSION_H__ -#define SSU_SESSION_H__ - -#include -#include -#include -#include "Crypto.h" -#include "I2NPProtocol.h" -#include "TransportSession.h" -#include "SSUData.h" - -namespace i2p -{ -namespace transport -{ - const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; - struct SSUHeader - { - uint8_t mac[16]; - uint8_t iv[16]; - uint8_t flag; - uint8_t time[4]; - - uint8_t GetPayloadType () const { return flag >> 4; }; - bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; - }; - - const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds - const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes - const int SSU_CLOCK_SKEW = 60; // in seconds - const int SSU_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust - const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; - - // payload types (4 bits) - const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; - const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; - const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; - const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; - const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; - const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; - const uint8_t PAYLOAD_TYPE_DATA = 6; - const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; - const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; - - // extended options - const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; - - enum SessionState - { - eSessionStateUnknown, - eSessionStateIntroduced, - eSessionStateEstablished, - eSessionStateClosed, - eSessionStateFailed - }; - - enum PeerTestParticipant - { - ePeerTestParticipantUnknown = 0, - ePeerTestParticipantAlice1, - ePeerTestParticipantAlice2, - ePeerTestParticipantBob, - ePeerTestParticipantCharlie - }; - - class SSUServer; - class SSUSession: public TransportSession, public std::enable_shared_from_this - { - public: - - SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router = nullptr, bool peerTest = false); - void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - ~SSUSession (); - - void Connect (); - void WaitForConnect (); - void Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to); // Alice to Charlie - void WaitForIntroduction (); - void Close (); - void Done (); - void Failed (); - const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - SSUServer& GetServer () { return m_Server; }; - - bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; - void SendI2NPMessages (const std::vector >& msgs); - void SendPeerTest (); // Alice - - SessionState GetState () const { return m_State; }; - size_t GetNumSentBytes () const { return m_NumSentBytes; }; - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - - void SendKeepAlive (); - uint32_t GetRelayTag () const { return m_RelayTag; }; - const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; - - void FlushData (); - void CleanUp (uint64_t ts); - - private: - - boost::asio::io_service& GetService (); - void CreateAESandMacKey (const uint8_t * pubKey); - size_t GetSSUHeaderSize (const uint8_t * buf) const; - void PostI2NPMessages (std::vector > msgs); - void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session - void ProcessSessionRequest (const uint8_t * buf, size_t len); - void SendSessionRequest (); - void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); - void ProcessSessionCreated (uint8_t * buf, size_t len); - void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); - void ProcessSessionConfirmed (const uint8_t * buf, size_t len); - void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); - void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); - void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); - void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); - void ProcessRelayResponse (const uint8_t * buf, size_t len); - void ProcessRelayIntro (const uint8_t * buf, size_t len); - void Established (); - void ScheduleConnectTimer (); - void HandleConnectTimer (const boost::system::error_code& ecode); - void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); - void ProcessData (uint8_t * buf, size_t len); - void SendSessionDestroyed (); - void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key - void Send (const uint8_t * buf, size_t size); - - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, - const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key - void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); - void DecryptSessionKey (uint8_t * buf, size_t len); - bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); - - void Reset (); - - static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size - - private: - - friend class SSUData; // TODO: change in later - SSUServer& m_Server; - const boost::asio::ip::udp::endpoint m_RemoteEndpoint; - boost::asio::deadline_timer m_ConnectTimer; - bool m_IsPeerTest; - SessionState m_State; - bool m_IsSessionKey; - uint32_t m_RelayTag; // received from peer - uint32_t m_SentRelayTag; // sent by us - i2p::crypto::CBCEncryption m_SessionKeyEncryption; - i2p::crypto::CBCDecryption m_SessionKeyDecryption; - i2p::crypto::AESKey m_SessionKey; - i2p::crypto::MACKey m_MacKey; - i2p::data::RouterInfo::IntroKey m_IntroKey; - SSUData m_Data; - bool m_IsDataReceived; - std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) - std::shared_ptr m_DHKeysPair; // X - for client and Y - for server - }; -} -} - -#endif