diff --git a/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt b/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt deleted file mode 100644 index a332805a..00000000 --- a/contrib/certificates/reseed/hiduser0_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFgTCCA2mgAwIBAgIETWAY1DANBgkqhkiG9w0BAQ0FADBxMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBAbWFp -bC5pMnAwHhcNMjExMjEzMTU0MDI3WhcNMzExMjExMTU0MDI3WjBxMQswCQYDVQQG -EwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5v -bnltb3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBA -bWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXnjJ8UQ0f -lHHpfPMiHofBPSuL4sbOJY6fOXwPhSg/h6THh9DS/ZWmJXQ3qRD0glDVtv4/Dr/9 -ldGQ5eltF9iCFXCQlMEy2HjQrBKq0nsl7RpYK12cyMaod0kkzCUk9ITLi9CmHM3Z -gQZcmG8TWjFEpDR+idx/QkQt2pcO4vzWlDit3Vh4ivnbX5jGQHbsVjQEMQWxr+pX -dsS+YQpjZ6RBmrooGTPO8QDOOeYLAn0lCjmffc/kzIH9E/p4/O0rOpyhVYbdxUD1 -5wkqN9l4yrtxmORG/PudnRQQ0r4TUq8vsxfGY0Euo9IbhgXF2Parel1ZhDxB1WZV -VwWtgLIh9jGA1UMa8SYKnEfp8LWNZ3b3mUUnZb3kMrLk6jGYRWNsHmamhd4mC7AZ -qf/8lOkEIw3bPd3YguCDRVcLui5BwIEZmqXg8uoESxfO/sW3pBrN/8M7MkTex9kN -vjitGDDXvenK27qmNgZxbBlX72yTSfys7XTYTLnxZC8AwdAo2Wz9Z6HhGiPonf2h -vZkc9ZxuE0jFIrsbJra4X7iyjXgi4vV4ARNg/9Ft6F4/OIbECgeDcBQqq4TlT2bZ -EfWVrBbqXoj5vNsLigIkd+AyUNwPYEcB5IFSiiOh98pC7BH3pg0m8U5YBjxe1i+9 -EQOOG0Qtx+JigXZHu6bGE0Twy9zy+UzoKQIDAQABoyEwHzAdBgNVHQ4EFgQUGK1b -0DkL6aLalcfBc/Uj/SF08C0wDQYJKoZIhvcNAQENBQADggIBAMpXM82bJDpH1TlH -TvhU3Z7nfZdvEhOQfujaFUYiuNripuEKcFGn948+DvAG0FUN+uNlJoqOVs8D7InD -gWlA9zpqw5Cl5Hij/Wns9QbXuAHJeA23fVUoaM2A6v9ifcIQ1A+rDuRQAo6/64KW -ChTg2e99RBpfGOyqgeh7tLLe0lPPekVpKHFuXabokaKRDuBcVHcUL4tWXe3dcyqa -Ej/PJrrS+nWL0EGZ4q80CEd2LPuDzPxNGCJt/R7ZfadENWajcgcXGceh1QBzozrB -SL/Ya6wF9SrsB7V/r5wX0LM4ZdDaLWbtmUe5Op0h/ZMH25Sa8xAXVz+O9L6sWSoO -FaiYTOvAiyyPz+nsxKa3xYryDHno7eKSt+hGOcaurhxbdZaEFY/CegEc73tCt9xK -e9qF8O/WkDLmixuErw3f5en4IfzGR7p3lJAwW/8WD8C6HS39h/eE7dVZNaWgtQnZ -SgGjgZMTJqTcQ3aZmfuCZefxGFok8w6AIkdbnd1pdMBRjYu8aXgl2hQSB9ZADDE9 -R5d3rXi0PkSFLIvsNjVa5KXrZk/tB0Hpfmepq7CufBqjP/LG9TieRoXzLYUKFF74 -QRwjP+y7AJ+VDUTpY1NV1P+k+2raubU2bOnLF3zL5DtyoyieGPhyeMMvp0fRIxdg -bSl5VHgPXHNM8mcnndMAuzvl7jEK ------END CERTIFICATE----- diff --git a/debian/changelog b/debian/changelog index 0fe779da..fd7b8d57 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,7 +2,7 @@ i2pd (2.54.0-1) unstable; urgency=medium * updated to version 2.54.0/0.9.64 --- orignal Sun, 6 Oct 2024 16:00:00 +0000 + -- orignal Sun, 6 Oct 2024 16:00:00 +0000 i2pd (2.53.1-1) unstable; urgency=medium diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index ab237613..5add5f1e 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -238,7 +238,6 @@ namespace config { "http://[324:71e:281a:9ed3::ace]:7070/," "http://[301:65b9:c7cd:9a36::1]:18801/," "http://[320:8936:ec1a:31f1::216]/," - "http://[306:3834:97b9:a00a::1]/," "http://[316:f9e0:f22e:a74f::216]/" ), "Reseed URLs through the Yggdrasil, separated by comma") ; diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 8ce290f1..2371b529 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -240,17 +240,12 @@ namespace crypto // x25519 X25519Keys::X25519Keys () { -#if OPENSSL_X25519 m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL); m_Pkey = nullptr; -#else - m_Ctx = BN_CTX_new (); -#endif } X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub) { -#if OPENSSL_X25519 m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); if (pub) @@ -260,29 +255,16 @@ namespace crypto size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); } -#else - m_Ctx = BN_CTX_new (); - memcpy (m_PrivateKey, priv, 32); - if (pub) - memcpy (m_PublicKey, pub, 32); - else - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } X25519Keys::~X25519Keys () { -#if OPENSSL_X25519 EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); -#else - BN_CTX_free (m_Ctx); -#endif } void X25519Keys::GenerateKeys () { -#if OPENSSL_X25519 if (m_Pkey) { EVP_PKEY_free (m_Pkey); @@ -294,16 +276,11 @@ namespace crypto m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); // TODO: do we really need to re-create m_Ctx? size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); -#else - RAND_bytes (m_PrivateKey, 32); - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { if (!pub || (pub[31] & 0x80)) return false; // not x25519 key -#if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); if (!pkey) return false; @@ -311,25 +288,17 @@ namespace crypto size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); EVP_PKEY_free (pkey); -#else - GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); -#endif return true; } void X25519Keys::GetPrivateKey (uint8_t * priv) const { -#if OPENSSL_X25519 size_t len = 32; EVP_PKEY_get_raw_private_key (m_Pkey, priv, &len); -#else - memcpy (priv, m_PrivateKey, 32); -#endif } void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic) { -#if OPENSSL_X25519 if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); @@ -339,11 +308,6 @@ namespace crypto size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); } -#else - memcpy (m_PrivateKey, priv, 32); - if (calculatePublic) - GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif } // ElGamal diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 13d331c8..75bd7bb2 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -31,7 +31,6 @@ #if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 # define OPENSSL_HKDF 1 # define OPENSSL_EDDSA 1 -# define OPENSSL_X25519 1 # if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL # define OPENSSL_SIPHASH 1 # endif @@ -70,13 +69,8 @@ namespace crypto private: uint8_t m_PublicKey[32]; -#if OPENSSL_X25519 EVP_PKEY_CTX * m_Ctx; EVP_PKEY * m_Pkey; -#else - BN_CTX * m_Ctx; - uint8_t m_PrivateKey[32]; -#endif bool m_IsElligatorIneligible = false; // true if definitely ineligible }; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 01ff2d2a..08acc0ed 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -37,7 +37,7 @@ namespace client int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE; int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE; int numTags = DEFAULT_TAGS_TO_SEND; - bool isHighBandwidth = true; + bool isHighBandwidth = true; std::shared_ptr > explicitPeers; try { @@ -168,7 +168,7 @@ namespace client LoadTags (); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -347,7 +347,24 @@ namespace client void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); + if (!msg) return; + bool empty = false; + { + std::lock_guard l(m_IncomingMsgsQueueMutex); + empty = m_IncomingMsgsQueue.empty (); + m_IncomingMsgsQueue.push_back (msg); + } + if (empty) + m_Service.post([s = shared_from_this ()]() + { + std::list > receivedMsgs; + { + std::lock_guard l(s->m_IncomingMsgsQueueMutex); + s->m_IncomingMsgsQueue.swap (receivedMsgs); + } + for (auto& it: receivedMsgs) + s->HandleGarlicMessage (it); + }); } void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) @@ -471,7 +488,7 @@ namespace client { auto it2 = m_LeaseSetRequests.find (key); if (it2 != m_LeaseSetRequests.end ()) - { + { request = it2->second; m_LeaseSetRequests.erase (it2); if (request->requestedBlindedKey) @@ -493,14 +510,14 @@ namespace client // publishing verification doesn't have requestedBlindedKey auto localLeaseSet = GetLeaseSetMt (); if (localLeaseSet->GetStoreHash () == key) - { - auto ls = std::make_shared (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, + { + auto ls = std::make_shared (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false); - leaseSet = ls; - } + leaseSet = ls; + } else LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key"); - } + } } else LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2"); @@ -511,14 +528,14 @@ namespace client } if (!request) - { + { auto it1 = m_LeaseSetRequests.find (key); if (it1 != m_LeaseSetRequests.end ()) - { + { request = it1->second; m_LeaseSetRequests.erase (it1); - } - } + } + } if (request) { request->requestTimeoutTimer.cancel (); @@ -550,7 +567,7 @@ namespace client LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found"); } - void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key, + void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key, std::shared_ptr request) { bool found = false; @@ -570,8 +587,8 @@ namespace client request->Complete (nullptr); m_LeaseSetRequests.erase (key); } - } - + } + void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID) { if (msgID == m_PublishReplyToken) @@ -592,7 +609,7 @@ namespace client { if (post) m_Service.post([s = shared_from_this ()]() { s->UpdateLeaseSet (); }); - else + else UpdateLeaseSet (); } @@ -631,7 +648,7 @@ namespace client if (!outbound || !inbound) { if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ()) - { + { LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill"); m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills); @@ -649,10 +666,10 @@ namespace client } else LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); - } + } else LogPrint (eLogDebug, "Destination: No tunnels in pool"); - + if (!floodfill || !outbound || !inbound) { // we can't publish now @@ -880,8 +897,8 @@ namespace client AddECIESx25519Key (replyKey, replyTag); else AddSessionKey (replyKey, replyTag); - - auto msg = WrapMessageForRouter (nextFloodfill, + + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); auto s = shared_from_this (); msg->onDrop = [s, dest, request]() @@ -890,7 +907,7 @@ namespace client { s->SendNextLeaseSetRequest (dest, request); }); - }; + }; request->outboundTunnel->SendTunnelDataMsgs ( { i2p::tunnel::TunnelMessageBlock @@ -953,7 +970,8 @@ namespace client CleanupExpiredTags (); CleanupRemoteLeaseSets (); CleanupDestination (); - m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT + + (m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0))); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } @@ -967,7 +985,7 @@ namespace client { if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired { - LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); } else @@ -988,6 +1006,7 @@ namespace client m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED), m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED), + m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS), m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0), m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0), m_ReadyChecker(service) @@ -1059,6 +1078,8 @@ namespace client it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED); if (it != params->end ()) m_StreamingInboundSpeed = std::stoi(it->second); + if (it != params->end ()) + m_StreamingMaxConcurrentStreams = std::stoi(it->second); it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end ()) m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true @@ -1438,11 +1459,11 @@ namespace client keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} ); auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch (); - if (publishedTimestamp <= m_LastPublishedTimestamp) + if (publishedTimestamp <= m_LastPublishedTimestamp) { LogPrint (eLogDebug, "Destination: LeaseSet update at the same second"); publishedTimestamp++; // force newer timestamp - } + } bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted); @@ -1514,6 +1535,8 @@ namespace client RunnableService ("Destination"), ClientDestination (GetIOService (), keys, isPublic, params) { + if (!GetNickname ().empty ()) + RunnableService::SetName (GetNickname ()); } RunnableClientDestination::~RunnableClientDestination () diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index c6a8cab7..8d49ef82 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -42,7 +42,8 @@ namespace client const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds - const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes + const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds + const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; // I2CP @@ -94,7 +95,9 @@ namespace client const int STREAMING_PROFILE_BULK = 1; // high bandwidth const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK; - + const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams"; + const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048; + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, @@ -197,6 +200,9 @@ namespace client std::unordered_map > m_RemoteLeaseSets; std::unordered_map > m_LeaseSetRequests; + std::list > m_IncomingMsgsQueue; + mutable std::mutex m_IncomingMsgsQueueMutex; + std::shared_ptr m_Pool; std::mutex m_LeaseSetMutex; std::shared_ptr m_LeaseSet; @@ -269,6 +275,7 @@ namespace client int GetStreamingAckDelay () const { return m_StreamingAckDelay; } int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; } int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; } + int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; } bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } // datagram @@ -305,9 +312,7 @@ namespace client std::unique_ptr m_StandardEncryptionKey; std::unique_ptr m_ECIESx25519EncryptionKey; - int m_StreamingAckDelay; - int m_StreamingOutboundSpeed; - int m_StreamingInboundSpeed; + int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams; bool m_IsStreamingAnswerPings; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 138d21e9..ffd47a31 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1112,6 +1112,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) { CleanupUnconfirmedLeaseSet (ts); + if (!m_Destination && ts > m_LastActivityTimestamp + ECIESX25519_SESSION_CREATE_TIMEOUT) return true; // m_LastActivityTimestamp is NS receive time + if (m_State != eSessionStateEstablished && m_SessionCreatedTimestamp && ts > m_SessionCreatedTimestamp + ECIESX25519_SESSION_ESTABLISH_TIMEOUT) return true; return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index bcc07b30..960dedb8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -30,6 +30,8 @@ namespace garlic const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received + const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds const int ECIESX25519_ACK_REQUEST_INTERVAL = 33000; // in milliseconds const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3; @@ -169,7 +171,7 @@ namespace garlic void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } void Terminate () { m_IsTerminated = true; } - void SetDestination (const i2p::data::IdentHash& dest) // TODO: + void SetDestination (const i2p::data::IdentHash& dest) { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); } @@ -224,7 +226,7 @@ namespace garlic uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) m_LastSentTimestamp = 0; // in milliseconds std::shared_ptr m_SendTagset, m_NSRSendTagset; - std::unique_ptr m_Destination;// TODO: might not need it + std::unique_ptr m_Destination;// must be set for NS if outgoing and NSR if incoming std::list > m_AckRequests; // incoming (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 3e0795d5..47edb755 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -457,86 +457,6 @@ namespace crypto } } -#if !OPENSSL_X25519 - BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const - { - BN_CTX_start (ctx); - auto x1 = BN_CTX_get (ctx); BN_copy (x1, u); - auto x2 = BN_CTX_get (ctx); BN_one (x2); - auto z2 = BN_CTX_get (ctx); BN_zero (z2); - auto x3 = BN_CTX_get (ctx); BN_copy (x3, u); - auto z3 = BN_CTX_get (ctx); BN_one (z3); - auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666); - auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx); - unsigned int swap = 0; - auto bits = BN_num_bits (k); - while(bits) - { - --bits; - auto k_t = BN_is_bit_set(k, bits) ? 1 : 0; - swap ^= k_t; - if (swap) - { - std::swap (x2, x3); - std::swap (z2, z3); - } - swap = k_t; - BN_mod_sub(tmp0, x3, z3, q, ctx); - BN_mod_sub(tmp1, x2, z2, q, ctx); - BN_mod_add(x2, x2, z2, q, ctx); - BN_mod_add(z2, x3, z3, q, ctx); - BN_mod_mul(z3, tmp0, x2, q, ctx); - BN_mod_mul(z2, z2, tmp1, q, ctx); - BN_mod_sqr(tmp0, tmp1, q, ctx); - BN_mod_sqr(tmp1, x2, q, ctx); - BN_mod_add(x3, z3, z2, q, ctx); - BN_mod_sub(z2, z3, z2, q, ctx); - BN_mod_mul(x2, tmp1, tmp0, q, ctx); - BN_mod_sub(tmp1, tmp1, tmp0, q, ctx); - BN_mod_sqr(z2, z2, q, ctx); - BN_mod_mul(z3, tmp1, c121666, q, ctx); - BN_mod_sqr(x3, x3, q, ctx); - BN_mod_add(tmp0, tmp0, z3, q, ctx); - BN_mod_mul(z3, x1, z2, q, ctx); - BN_mod_mul(z2, tmp1, tmp0, q, ctx); - } - if (swap) - { - std::swap (x2, x3); - std::swap (z2, z3); - } - BN_mod_inverse (z2, z2, q, ctx); - BIGNUM * res = BN_new (); // not from ctx - BN_mod_mul(res, x2, z2, q, ctx); - BN_CTX_end (ctx); - return res; - } - - void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const - { - BIGNUM * p1 = DecodeBN<32> (p); - uint8_t k[32]; - memcpy (k, e, 32); - k[0] &= 248; k[31] &= 127; k[31] |= 64; - BIGNUM * n = DecodeBN<32> (k); - BIGNUM * q1 = ScalarMul (p1, n, ctx); - EncodeBN (q1, buf, 32); - BN_free (p1); BN_free (n); BN_free (q1); - } - - void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const - { - BIGNUM *p1 = BN_new (); BN_set_word (p1, 9); - uint8_t k[32]; - memcpy (k, e, 32); - k[0] &= 248; k[31] &= 127; k[31] |= 64; - BIGNUM * n = DecodeBN<32> (k); - BIGNUM * q1 = ScalarMul (p1, n, ctx); - EncodeBN (q1, buf, 32); - BN_free (p1); BN_free (n); BN_free (q1); - } -#endif - void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded) { BN_CTX * ctx = BN_CTX_new (); diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 470d802f..9c0ad801 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -84,10 +84,7 @@ namespace crypto EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const; EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const; void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const; -#if !OPENSSL_X25519 - void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519 - void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; -#endif + void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 @@ -115,11 +112,6 @@ namespace crypto BIGNUM * DecodeBN (const uint8_t * buf) const; void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const; -#if !OPENSSL_X25519 - // for x25519 - BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const; -#endif - private: BIGNUM * q, * l, * d, * I; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 04884acd..b6f6d131 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -426,7 +426,8 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0), + m_NumRatchetInboundTags (0) // 0 means standard { } @@ -539,9 +540,17 @@ namespace garlic else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { // otherwise ECIESx25519 - auto session = std::make_shared (this, false); // incoming - if (!session->HandleNextMessage (buf, length, nullptr, 0)) - LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > m_LastIncomingSessionTimestamp + INCOMING_SESSIONS_MINIMAL_INTERVAL) + { + auto session = std::make_shared (this, false); // incoming + if (session->HandleNextMessage (buf, length, nullptr, 0)) + m_LastIncomingSessionTimestamp = ts; + else + LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); + } + else + LogPrint (eLogWarning, "Garlic: Incoming sessions come too ofter"); } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -737,7 +746,8 @@ namespace garlic } std::shared_ptr GarlicDestination::GetRoutingSession ( - std::shared_ptr destination, bool attachLeaseSet) + std::shared_ptr destination, bool attachLeaseSet, + bool requestNewIfNotFound) { if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) @@ -752,16 +762,17 @@ namespace garlic if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) { LogPrint (eLogDebug, "Garlic: Session restarted"); + requestNewIfNotFound = true; // it's not a new session session = nullptr; } } - if (!session) + if (!session && requestNewIfNotFound) { session = std::make_shared (this, true); session->SetRemoteStaticKey (staticKey); } - if (destination->IsDestination ()) - session->SetDestination (destination->GetIdentHash ()); // TODO: remove + if (session && destination->IsDestination ()) + session->SetDestination (destination->GetIdentHash ()); // NS or NSR return session; } else diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 80fc15da..57c3fddc 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -52,6 +52,7 @@ namespace garlic const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds + const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds struct SessionTag: public i2p::data::Tag<32> { @@ -234,7 +235,8 @@ namespace garlic int GetNumTags () const { return m_NumTags; }; void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; }; int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; }; - std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); + std::shared_ptr GetRoutingSession (std::shared_ptr destination, + bool attachLeaseSet, bool requestNewIfNotFound = true); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessageForRouter (std::shared_ptr router, @@ -284,6 +286,7 @@ namespace garlic std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet + uint64_t m_LastIncomingSessionTimestamp; // in millseconds // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 990781bc..258d3ada 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -103,6 +103,7 @@ namespace http bool URL::parse(std::string_view url) { + if (url.empty ()) return false; std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ if(url.at(0) != '/' || pos_p > 0) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 198f798b..e97a3596 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -10,20 +10,15 @@ #include #include "Base.h" #include "Log.h" -#include "Crypto.h" #include "I2PEndian.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.hpp" #include "Tunnel.h" -#include "Transports.h" -#include "Garlic.h" -#include "ECIESX25519AEADRatchetSession.h" +#include "TransitTunnel.h" #include "I2NPProtocol.h" #include "version.h" -using namespace i2p::transport; - namespace i2p { std::shared_ptr NewI2NPMessage () @@ -376,355 +371,6 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } - static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) - { - for (int i = 0; i < num; i++) - { - uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; - if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) - { - LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); - if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) - { - LogPrint (eLogWarning, "I2NP: Failed to decrypt tunnel build record"); - return false; - } - if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours - !(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint - { - LogPrint (eLogWarning, "I2NP: Next ident is ours in tunnel build record"); - return false; - } - uint8_t retCode = 0; - // decide if we should accept tunnel - bool accept = i2p::context.AcceptsTunnels (); - if (accept) - { - auto congestionLevel = i2p::context.GetCongestionLevel (false); - if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) - { - if (congestionLevel < CONGESTION_LEVEL_FULL) - { - // random reject depending on congestion level - int level = i2p::tunnel::tunnels.GetRng ()() % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; - if (congestionLevel > level) - accept = false; - } - else - accept = false; - } - } - // replace record to reply - if (accept) - { - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) - retCode = 30; - } - else - retCode = 30; // always reject with bandwidth reason (30) - - memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - // encrypt reply - i2p::crypto::CBCEncryption encryption; - for (int j = 0; j < num; j++) - { - uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - if (j == i) - { - uint8_t nonce[12]; - memset (nonce, 0, 12); - auto& noiseState = i2p::context.GetCurrentNoiseState (); - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); - return false; - } - } - else - { - encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); - } - } - return true; - } - } - return false; - } - - static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); - if (num > i2p::tunnel::MAX_NUM_RECORDS) - { - LogPrint (eLogError, "I2NP: Too many records in VaribleTunnelBuild message ", num); - return; - } - if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) - { - LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); - return; - } - - auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - } - else - { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) - { - if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - } - } - - static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) - { - LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); - } - - static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); - if (num > i2p::tunnel::MAX_NUM_RECORDS) - { - LogPrint (eLogError, "I2NP: Too many records in TunnelBuildReply message ", num); - return; - } - size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; - if (len < num*recordSize + 1) - { - LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); - return; - } - - auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); - if (tunnel) - { - // reply for outbound tunnel - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddOutboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - } - else - LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); - } - - static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) - { - int num = buf[0]; - LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); - if (num > i2p::tunnel::MAX_NUM_RECORDS) - { - LogPrint (eLogError, "I2NP: Too many records in ShortTunnelBuild message ", num); - return; - } - if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) - { - LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); - return; - } - auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); - if (tunnel) - { - // endpoint of inbound tunnel - LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); - if (tunnel->HandleTunnelBuildResponse (buf, len)) - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); - tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddInboundTunnel (tunnel); - } - else - { - LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); - tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); - } - return; - } - const uint8_t * record = buf + 1; - for (int i = 0; i < num; i++) - { - if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) - { - LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); - uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) - { - LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); - return; - } - if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES - { - LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); - return; - } - auto& noiseState = i2p::context.GetCurrentNoiseState (); - uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305 - i2p::crypto::AESKey layerKey, ivKey; // AES - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); - memcpy (replyKey, noiseState.m_CK + 32, 32); - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); - memcpy (layerKey, noiseState.m_CK + 32, 32); - bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; - if (isEndpoint) - { - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); - memcpy (ivKey, noiseState.m_CK + 32, 32); - } - else - { - if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours - { - LogPrint (eLogWarning, "I2NP: Next ident is ours in short request record"); - return; - } - memcpy (ivKey, noiseState.m_CK , 32); - } - - // check if we accept this tunnel - std::shared_ptr transitTunnel; - uint8_t retCode = 0; - if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL) - retCode = 30; - if (!retCode) - { - // create new transit tunnel - transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - layerKey, ivKey, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) - retCode = 30; - } - - // encrypt reply - uint8_t nonce[12]; - memset (nonce, 0, 12); - uint8_t * reply = buf + 1; - for (int j = 0; j < num; j++) - { - nonce[4] = j; // nonce is record # - if (j == i) - { - memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); - return; - } - } - else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - // send reply - auto onDrop = [transitTunnel]() - { - if (transitTunnel) - { - auto t = transitTunnel->GetCreationTime (); - if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT) - // make transit tunnel expired - transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT); - } - }; - if (isEndpoint) - { - auto replyMsg = NewI2NPShortMessage (); - replyMsg->Concat (buf, len); - replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); - if (transitTunnel) replyMsg->onDrop = onDrop; - if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? - { - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); - uint64_t tag; - memcpy (&tag, noiseState.m_CK, 8); - // we send it to reply tunnel - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); - } - else - { - // IBGW is local - uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); - auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); - if (tunnel) - { - tunnel->SendTunnelDataMsg (replyMsg); - tunnel->FlushTunnelDataMsgs (); - } - else - LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); - } - } - else - { - auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); - if (transitTunnel) msg->onDrop = onDrop; - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg); - } - return; - } - record += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - } - std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPTunnelMessage (false); @@ -820,41 +466,6 @@ namespace i2p return l; } - void HandleTunnelBuildI2NPMessage (std::shared_ptr msg) - { - if (msg) - { - uint8_t typeID = msg->GetTypeID(); - uint32_t msgID = msg->GetMsgID(); - LogPrint (eLogDebug, "I2NP: Handling tunnel build message with len=", msg->GetLength(),", type=", (int)typeID, ", msgID=", (unsigned int)msgID); - uint8_t * payload = msg->GetPayload(); - auto size = msg->GetPayloadLength(); - switch (typeID) - { - case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msgID, payload, size); - break; - case eI2NPShortTunnelBuild: - HandleShortTunnelBuildMsg (msgID, payload, size); - break; - case eI2NPVariableTunnelBuildReply: - HandleTunnelBuildReplyMsg (msgID, payload, size, false); - break; - case eI2NPShortTunnelBuildReply: - HandleTunnelBuildReplyMsg (msgID, payload, size, true); - break; - case eI2NPTunnelBuild: - HandleTunnelBuildMsg (payload, size); - break; - case eI2NPTunnelBuildReply: - // TODO: - break; - default: - LogPrint (eLogError, "I2NP: Unexpected message with type", (int)typeID, " during handling TBM; skipping"); - } - } - } - void HandleI2NPMessage (std::shared_ptr msg) { if (msg) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 5971ce17..911a53bf 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -316,7 +316,6 @@ namespace tunnel std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); size_t GetI2NPMessageLength (const uint8_t * msg, size_t len); - void HandleTunnelBuildI2NPMessage (std::shared_ptr msg); void HandleI2NPMessage (std::shared_ptr msg); class I2NPMessagesHandler diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 66faed84..ee1964fc 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -425,6 +425,16 @@ namespace data if (offset + 1 > len) return 0; int numLeases = buf[offset]; offset++; auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (GetExpirationTime () > ts + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Expiration time is from future ", GetExpirationTime ()/1000LL); + return 0; + } + if (ts > m_PublishedTimestamp*1000LL + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Published time is too old ", m_PublishedTimestamp); + return 0; + } if (IsStoreLeases ()) { UpdateLeasesBegin (); @@ -435,6 +445,11 @@ namespace data lease.tunnelGateway = buf + offset; offset += 32; // gateway lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date + if (lease.endDate > ts + LEASESET_EXPIRATION_TIME_THRESHOLD) + { + LogPrint (eLogWarning, "LeaseSet2: Lease end date is from future ", lease.endDate); + return 0; + } UpdateLease (lease, ts); } UpdateLeasesEnd (); diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 7eea3aed..a365ae77 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -62,7 +62,8 @@ namespace data const size_t LEASE_SIZE = 44; // 32 + 4 + 8 const size_t LEASE2_SIZE = 40; // 32 + 4 + 4 const uint8_t MAX_NUM_LEASES = 16; - + const uint64_t LEASESET_EXPIRATION_TIME_THRESHOLD = 12*60*1000; // in milliseconds + const uint8_t NETDB_STORE_TYPE_LEASESET = 1; class LeaseSet: public RoutingDestination { @@ -180,7 +181,7 @@ namespace data private: uint8_t m_StoreType; - uint32_t m_PublishedTimestamp = 0; + uint32_t m_PublishedTimestamp = 0; // seconds bool m_IsPublic = true, m_IsPublishedEncrypted = false; std::shared_ptr m_TransientVerifier; CryptoKeyType m_EncryptionType; diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index a05469f1..33c33596 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -375,6 +375,8 @@ namespace transport m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCP2Session (shared_from_this ()); + if (!m_IntermediateQueue.empty ()) + m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue); for (auto& it: m_SendQueue) it->Drop (); m_SendQueue.clear (); @@ -1207,7 +1209,7 @@ namespace transport void NTCP2Session::MoveSendQueue (std::shared_ptr other) { if (!other || m_SendQueue.empty ()) return; - std::vector > msgs; + std::list > msgs; auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_SendQueue) if (!it->IsExpired (ts)) @@ -1216,7 +1218,7 @@ namespace transport it->Drop (); m_SendQueue.clear (); if (!msgs.empty ()) - other->PostI2NPMessages (msgs); + other->SendI2NPMessages (msgs); } size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len) @@ -1297,20 +1299,42 @@ namespace transport m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go } - void NTCP2Session::SendI2NPMessages (const std::vector >& msgs) + void NTCP2Session::SendI2NPMessages (std::list >& msgs) { - m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs)); + if (m_IsTerminated || msgs.empty ()) + { + msgs.clear (); + return; + } + bool empty = false; + { + std::lock_guard l(m_IntermediateQueueMutex); + empty = m_IntermediateQueue.empty (); + m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); + } + if (empty) + m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ())); } - void NTCP2Session::PostI2NPMessages (std::vector > msgs) + void NTCP2Session::PostI2NPMessages () { if (m_IsTerminated) return; + std::list > msgs; + { + std::lock_guard l(m_IntermediateQueueMutex); + m_IntermediateQueue.swap (msgs); + } bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2; - for (auto it: msgs) - if (isSemiFull && it->onDrop) - it->Drop (); // drop earlier because we can handle it - else - m_SendQueue.push_back (std::move (it)); + if (isSemiFull) + { + for (auto it: msgs) + if (it->onDrop) + it->Drop (); // drop earlier because we can handle it + else + m_SendQueue.push_back (std::move (it)); + } + else + m_SendQueue.splice (m_SendQueue.end (), msgs); if (!m_IsSending && m_IsEstablished) SendQueue (); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index f7912b54..27acb529 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -153,7 +153,7 @@ namespace transport void ServerLogin (); // Bob void SendLocalRouterInfo (bool update) override; // after handshake or by update - void SendI2NPMessages (const std::vector >& msgs) override; + void SendI2NPMessages (std::list >& msgs) override; void MoveSendQueue (std::shared_ptr other); private: @@ -196,7 +196,7 @@ namespace transport void SendRouterInfo (); void SendTermination (NTCP2TerminationReason reason); void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void PostI2NPMessages (std::vector > msgs); + void PostI2NPMessages (); private: @@ -229,7 +229,10 @@ namespace transport bool m_IsSending, m_IsReceiving; std::list > m_SendQueue; uint64_t m_NextRouterInfoResendTime; // seconds since epoch - + + std::list > m_IntermediateQueue; // from transports + mutable std::mutex m_IntermediateQueueMutex; + uint16_t m_PaddingSizes[16]; int m_NextPaddingSize; }; diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 341d617e..18d6308b 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -384,8 +384,7 @@ namespace data if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) { - if (leaseSet->IsPublic () && !leaseSet->IsExpired () && - i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ()) + if (leaseSet->IsPublic () && !leaseSet->IsExpired ()) { // TODO: implement actual update if (CheckLogLevel (eLogInfo)) @@ -480,7 +479,7 @@ namespace data void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills) { LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64()); - std::vector > requests; + std::list > requests; i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); i2p::data::IdentHash ih = ri.GetIdentHash(); diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 27925434..59d60c6d 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -206,10 +206,9 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } - bool RouterProfile::IsDeclinedRecently () + bool RouterProfile::IsDeclinedRecently (uint64_t ts) { if (!m_LastDeclineTime) return false; - auto ts = i2p::util::GetSecondsSinceEpoch (); if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL || ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime) m_LastDeclineTime = 0; @@ -218,7 +217,10 @@ namespace data bool RouterProfile::IsBad () { - if (IsDeclinedRecently () || IsUnreachable () || m_IsDuplicated) return true; + if (IsUnreachable () || m_IsDuplicated) return true; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > PEER_PROFILE_MAX_DECLINED_INTERVAL + m_LastDeclineTime) return false; + if (IsDeclinedRecently (ts)) return true; auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index be674d95..5d85cec3 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -38,11 +38,13 @@ namespace data const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 900; // in seconds (15 minutes) const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT = 5400; // in seconds (1.5 hours) const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes) - const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes) + const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes) + const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours) const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes) const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes) const int PEER_PROFILE_USEFUL_THRESHOLD = 3; - + const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined + class RouterProfile { public: @@ -81,7 +83,7 @@ namespace data bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; bool IsLowPartcipationRate () const; bool IsLowReplyRate () const; - bool IsDeclinedRecently (); + bool IsDeclinedRecently (uint64_t ts); private: diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index f5f8b4fb..558e9608 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -323,9 +323,11 @@ namespace i2p case eRouterStatusFirewalled: SetUnreachable (true, false); // ipv4 break; + case eRouterStatusMesh: + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable); + break; case eRouterStatusProxy: - m_AcceptsTunnels = false; - UpdateCongestion (); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable); break; default: ; @@ -1489,7 +1491,7 @@ namespace i2p void RouterContext::UpdateCongestion () { auto c = i2p::data::RouterInfo::eLowCongestion; - if (!AcceptsTunnels () || !m_ShareRatio || (m_Error == eRouterErrorSymmetricNAT && !SupportsV6 () && !SupportsMesh ())) + if (!AcceptsTunnels () || !m_ShareRatio) c = i2p::data::RouterInfo::eRejectAll; else { @@ -1508,7 +1510,7 @@ namespace i2p if (m_CleanupTimer) { m_CleanupTimer->cancel (); - m_CleanupTimer->expires_from_now (boost::posix_time::minutes(ROUTER_INFO_CLEANUP_INTERVAL)); + m_CleanupTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CLEANUP_INTERVAL)); m_CleanupTimer->async_wait (std::bind (&RouterContext::HandleCleanupTimer, this, std::placeholders::_1)); } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index f828a182..0dfef254 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -37,7 +37,7 @@ namespace garlic const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds - const int ROUTER_INFO_CLEANUP_INTERVAL = 5; // in minutes + const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds enum RouterStatus { diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index c8f2909b..a53c877c 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -457,13 +457,20 @@ namespace transport void SSU2Server::InsertToReceivedPacketsQueue (std::list& packets) { if (packets.empty ()) return; - bool empty = false; + size_t queueSize = 0; { std::lock_guard l(m_ReceivedPacketsQueueMutex); - empty = m_ReceivedPacketsQueue.empty (); - m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets); + queueSize = m_ReceivedPacketsQueue.size (); + if (queueSize < SSU2_MAX_RECEIVED_QUEUE_SIZE) + m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets); + else + { + LogPrint (eLogError, "SSU2: Received queue size ", queueSize, " exceeds max size", SSU2_MAX_RECEIVED_QUEUE_SIZE); + m_PacketsPool.ReleaseMt (packets); + queueSize = 0; // invoke processing just in case + } } - if (empty) + if (!queueSize) GetService ().post([this]() { HandleReceivedPacketsQueue (); }); } diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 319a4780..95f053a9 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -37,6 +37,7 @@ namespace transport const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024; const size_t SSU2_MAX_NUM_INTRODUCERS = 3; const size_t SSU2_MIN_RECEIVED_PACKET_SIZE = 40; // 16 byte short header + 8 byte minimum payload + 16 byte MAC + const size_t SSU2_MAX_RECEIVED_QUEUE_SIZE = 2500; // in packets const int SSU2_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds diff --git a/libi2pd/SSU2OutOfSession.cpp b/libi2pd/SSU2OutOfSession.cpp index 262f93a9..651ed76c 100644 --- a/libi2pd/SSU2OutOfSession.cpp +++ b/libi2pd/SSU2OutOfSession.cpp @@ -111,7 +111,6 @@ namespace transport SendPeerTest (7, buf + offset, len - offset); else LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); - GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); GetServer ().RequestRemoveSession (GetConnID ()); break; } @@ -141,7 +140,6 @@ namespace transport } } } - GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); GetServer ().RequestRemoveSession (GetConnID ()); break; } @@ -188,6 +186,7 @@ namespace transport i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16); // send GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); + UpdateNumSentBytes (payloadSize + 32); } void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed) @@ -309,6 +308,7 @@ namespace transport i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16); // send GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); + UpdateNumSentBytes (payloadSize + 32); } void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen) diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 3489a6ba..96f38217 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -293,6 +293,8 @@ namespace transport m_SentHandshakePacket.reset (nullptr); m_SessionConfirmedFragment.reset (nullptr); m_PathChallenge.reset (nullptr); + if (!m_IntermediateQueue.empty ()) + m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue); for (auto& it: m_SendQueue) it->Drop (); m_SendQueue.clear (); @@ -335,13 +337,14 @@ namespace transport SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT); SendQueue (); transports.PeerConnected (shared_from_this ()); + + LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established"); if (m_OnEstablished) { m_OnEstablished (); m_OnEstablished = nullptr; } - LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established"); } void SSU2Session::Done () @@ -372,14 +375,31 @@ namespace transport } - void SSU2Session::SendI2NPMessages (const std::vector >& msgs) + void SSU2Session::SendI2NPMessages (std::list >& msgs) { - m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), msgs)); + if (m_State == eSSU2SessionStateTerminated || msgs.empty ()) + { + msgs.clear (); + return; + } + bool empty = false; + { + std::lock_guard l(m_IntermediateQueueMutex); + empty = m_IntermediateQueue.empty (); + m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); + } + if (empty) + m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this ())); } - void SSU2Session::PostI2NPMessages (std::vector > msgs) + void SSU2Session::PostI2NPMessages () { if (m_State == eSSU2SessionStateTerminated) return; + std::list > msgs; + { + std::lock_guard l(m_IntermediateQueueMutex); + m_IntermediateQueue.swap (msgs); + } uint64_t mts = i2p::util::GetMonotonicMicroseconds (); bool isSemiFull = false; if (m_SendQueue.size ()) @@ -393,16 +413,24 @@ namespace transport " is semi-full (size = ", m_SendQueue.size (), ", lag = ", queueLag / 1000, ", rtt = ", (int)m_RTT, ")"); } } - for (auto it: msgs) - { - if (isSemiFull && it->onDrop) - it->Drop (); // drop earlier because we can handle it - else + if (isSemiFull) + { + for (auto it: msgs) { - it->SetEnqueueTime (mts); - m_SendQueue.push_back (std::move (it)); + if (it->onDrop) + it->Drop (); // drop earlier because we can handle it + else + { + it->SetEnqueueTime (mts); + m_SendQueue.push_back (std::move (it)); + } } - } + } + else + { + for (auto& it: msgs) it->SetEnqueueTime (mts); + m_SendQueue.splice (m_SendQueue.end (), msgs); + } if (IsEstablished ()) { SendQueue (); @@ -415,7 +443,7 @@ namespace transport void SSU2Session::MoveSendQueue (std::shared_ptr other) { if (!other || m_SendQueue.empty ()) return; - std::vector > msgs; + std::list > msgs; auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_SendQueue) if (!it->IsExpired (ts)) @@ -424,7 +452,7 @@ namespace transport it->Drop (); m_SendQueue.clear (); if (!msgs.empty ()) - other->PostI2NPMessages (msgs); + other->SendI2NPMessages (msgs); } bool SSU2Session::SendQueue () @@ -1917,21 +1945,28 @@ namespace transport void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len) { // we are Bob + auto mts = i2p::util::GetMillisecondsSinceEpoch (); + uint32_t nonce = bufbe32toh (buf + 1); // nonce uint32_t relayTag = bufbe32toh (buf + 5); // relay tag auto session = m_Server.FindRelaySession (relayTag); if (!session) { LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found"); // send relay response back to Alice - uint8_t payload[SSU2_MAX_PACKET_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, - eSSU2RelayResponseCodeBobRelayTagNotFound, bufbe32toh (buf + 1), 0, false); - payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); - SendData (payload, payloadSize); + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + packet->payloadSize = CreateAckBlock (packet->payload, m_MaxPayloadSize); + packet->payloadSize += CreateRelayResponseBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, + eSSU2RelayResponseCodeBobRelayTagNotFound, nonce, 0, false); + packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize); + uint32_t packetNum = SendData (packet->payload, packet->payloadSize); + if (m_RemoteVersion >= SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION) + { + // sometimes Alice doesn't ack this RelayResponse in older versions + packet->sendTime = mts; + m_SentPackets.emplace (packetNum, packet); + } return; } - auto mts = i2p::util::GetMillisecondsSinceEpoch (); - uint32_t nonce = bufbe32toh (buf + 1); if (session->m_RelaySessions.emplace (nonce, std::make_pair (shared_from_this (), mts/1000)).second) { // send relay intro to Charlie diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index d54731dc..4b3139a7 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -261,7 +261,7 @@ namespace transport void FlushData (); void Done () override; void SendLocalRouterInfo (bool update) override; - void SendI2NPMessages (const std::vector >& msgs) override; + void SendI2NPMessages (std::list >& msgs) override; void MoveSendQueue (std::shared_ptr other); uint32_t GetRelayTag () const override { return m_RelayTag; }; size_t Resend (uint64_t ts); // return number of resent packets @@ -307,7 +307,7 @@ namespace transport void Established (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); - void PostI2NPMessages (std::vector > msgs); + void PostI2NPMessages (); bool SendQueue (); // returns true if ack block was sent bool SendFragmentedMessage (std::shared_ptr msg); void ResendHandshakePacket (); @@ -381,6 +381,8 @@ namespace transport std::unordered_map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice std::list > m_SendQueue; i2p::I2NPMessagesHandler m_Handler; + std::list > m_IntermediateQueue; // from transports + mutable std::mutex m_IntermediateQueueMutex; bool m_IsDataReceived; double m_RTT; int m_MsgLocalExpirationTimeout; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 966c172d..ece2979d 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -71,7 +71,7 @@ namespace stream m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0), m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed - m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), + m_Status (eStreamStatusNew), m_IsIncoming (false), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), @@ -99,7 +99,7 @@ namespace stream m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_DropWindowDelaySequenceNumber (0), m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_PreviousReceivedSequenceNumber (-1), m_LastConfirmedReceivedSequenceNumber (0), // for limit inbound speed - m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), + m_Status (eStreamStatusNew), m_IsIncoming (true), m_IsAckSendScheduled (false), m_IsNAcked (false), m_IsFirstACK (false), m_IsResendNeeded (false), m_IsFirstRttSample (false), m_IsSendTime (true), m_IsWinDropped (false), m_IsTimeOutResend (false), m_IsImmediateAckRequested (false), m_IsRemoteLeaseChangeInProgress (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_SendTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), @@ -705,7 +705,8 @@ namespace stream void Stream::SendBuffer () { - ScheduleSend (); + if (m_RemoteLeaseSet) // don't scheudle send for first SYN for incoming stream + ScheduleSend (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); int numMsgs = m_WindowSize - m_SentPackets.size (); if (numMsgs <= 0 || !m_IsSendTime) // window is full @@ -756,8 +757,8 @@ namespace stream if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; if (m_RemoteLeaseSet) { - m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); - m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming); + m_MTU = (m_RoutingSession && m_RoutingSession->IsRatchets ()) ? STREAMING_MTU_RATCHETS : STREAMING_MTU; } uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; @@ -1114,7 +1115,15 @@ namespace stream } } if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent - m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); + { + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming || m_SequenceNumber > 1); + if (!m_RoutingSession) + { + LogPrint (eLogError, "Streaming: Can't obtain routing session, sSID=", m_SendStreamID); + Terminate (); + return; + } + } if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first @@ -1328,98 +1337,105 @@ namespace stream void Stream::ResendPacket () { - // check for resend attempts - if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) - { - LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); - m_Status = eStreamStatusReset; - Close (); - return; - } + // check for resend attempts + if (m_IsIncoming && m_SequenceNumber == 1 && m_NumResendAttempts > 0) + { + LogPrint (eLogWarning, "Streaming: SYNACK packet was not ACKed after ", m_NumResendAttempts, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + m_Status = eStreamStatusReset; + Close (); + return; + } + if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) + { + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + m_Status = eStreamStatusReset; + Close (); + return; + } - // collect packets to resend - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - std::vector packets; - if (m_IsNAcked) + // collect packets to resend + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + std::vector packets; + if (m_IsNAcked) + { + for (auto it : m_NACKedPackets) { - for (auto it : m_NACKedPackets) + if (ts >= it->sendTime + m_RTO) { - if (ts >= it->sendTime + m_RTO) - { - if (ts < it->sendTime + m_RTO*2) - it->resent = true; - else - it->resent = false; - it->sendTime = ts; - packets.push_back (it); - if ((int)packets.size () >= m_NumPacketsToSend) break; - } + if (ts < it->sendTime + m_RTO*2) + it->resent = true; + else + it->resent = false; + it->sendTime = ts; + packets.push_back (it); + if ((int)packets.size () >= m_NumPacketsToSend) break; } } - else + } + else + { + for (auto it : m_SentPackets) { - for (auto it : m_SentPackets) + if (ts >= it->sendTime + m_RTO) { - if (ts >= it->sendTime + m_RTO) - { - if (ts < it->sendTime + m_RTO*2) - it->resent = true; - else - it->resent = false; - it->sendTime = ts; - packets.push_back (it); - if ((int)packets.size () >= m_NumPacketsToSend) break; - } + if (ts < it->sendTime + m_RTO*2) + it->resent = true; + else + it->resent = false; + it->sendTime = ts; + packets.push_back (it); + if ((int)packets.size () >= m_NumPacketsToSend) break; } } - - // select tunnels if necessary and send - if (packets.size () > 0 && m_IsSendTime) + } + + // select tunnels if necessary and send + if (packets.size () > 0 && m_IsSendTime) + { + if (m_IsNAcked) m_NumResendAttempts = 1; + else if (m_IsTimeOutResend) m_NumResendAttempts++; + if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) + { + // loss-based CC + if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED) + ProcessWindowDrop (); + } + else if (m_IsTimeOutResend) { - if (m_IsNAcked) m_NumResendAttempts = 1; - else if (m_IsTimeOutResend) m_NumResendAttempts++; - if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) + m_IsTimeOutResend = false; + m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change + m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; + m_LastWindowDropSize = 0; + m_WindowIncCounter = 0; + m_IsWinDropped = true; + m_IsFirstRttSample = true; + m_DropWindowDelaySequenceNumber = 0; + m_IsFirstACK = true; + UpdatePacingTime (); + if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); + if (m_NumResendAttempts & 1) { - // loss-based CC - if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED) - ProcessWindowDrop (); + // pick another outbound tunnel + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, + ", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); } - else if (m_IsTimeOutResend) + else { - m_IsTimeOutResend = false; - m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change - m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; - m_LastWindowDropSize = 0; - m_WindowIncCounter = 0; - m_IsWinDropped = true; - m_IsFirstRttSample = true; - m_DropWindowDelaySequenceNumber = 0; - m_IsFirstACK = true; - UpdatePacingTime (); - if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); - if (m_NumResendAttempts & 1) - { - // pick another outbound tunnel - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); - LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, - ", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); - } - else - { - CancelRemoteLeaseChange (); - UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, - ", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); - } + CancelRemoteLeaseChange (); + UpdateCurrentRemoteLease (); // pick another lease + LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, + ", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); } - SendPackets (packets); - m_LastSendTime = ts; - m_IsSendTime = false; - if (m_IsNAcked || m_IsResendNeeded) ScheduleSend (); } - else - SendBuffer (); - if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); + SendPackets (packets); + m_LastSendTime = ts; + m_IsSendTime = false; + if (m_IsNAcked || m_IsResendNeeded) ScheduleSend (); + } + else + SendBuffer (); + if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); } void Stream::ScheduleAck (int timeout) @@ -1471,17 +1487,26 @@ namespace stream if (!remoteLeaseSet) { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); - if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) - { - m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( - std::make_shared(m_RemoteIdentity)); - return; // we keep m_RemoteLeaseSet for possible next request + if (!m_IsIncoming) // outgoing + { + if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) + { + m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( + std::make_shared(m_RemoteIdentity)); + return; // we keep m_RemoteLeaseSet for possible next request + } + else + { + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + } } - else + else // incoming { - m_RemoteLeaseSet = nullptr; - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt - } + // just close the socket without sending FIN or RST + m_Status = eStreamStatusClosed; + AsyncClose (); + } } else { @@ -1696,9 +1721,20 @@ namespace stream DeletePacket (packet); // drop it, because previous should be connected return; } + if (m_Owner->GetStreamingMaxConcurrentStreams () > 0 && (int)m_Streams.size () > m_Owner->GetStreamingMaxConcurrentStreams ()) + { + LogPrint(eLogWarning, "Streaming: Number of streams exceeds ", m_Owner->GetStreamingMaxConcurrentStreams ()); + DeletePacket (packet); + return; + } auto incomingStream = CreateNewIncomingStream (receiveStreamID); incomingStream->HandleNextPacket (packet); // SYN - auto ident = incomingStream->GetRemoteIdentity(); + if (!incomingStream->GetRemoteLeaseSet ()) + { + LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated"); + incomingStream->Terminate (); // can't send FIN anyway + return; + } // handle saved packets if any { @@ -1800,7 +1836,8 @@ namespace stream { std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); - m_IncomingStreams.erase (stream->GetSendStreamID ()); + if (stream->IsIncoming ()) + m_IncomingStreams.erase (stream->GetSendStreamID ()); if (m_LastStream == stream) m_LastStream = nullptr; } auto ts = i2p::util::GetSecondsSinceEpoch (); diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index a686d71b..11fc04d1 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -186,6 +186,7 @@ namespace stream std::shared_ptr GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; + bool IsIncoming () const { return m_IsIncoming; }; StreamStatus GetStatus () const { return m_Status; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; void ResetRoutingPath (); @@ -262,6 +263,7 @@ namespace stream int32_t m_PreviousReceivedSequenceNumber; int32_t m_LastConfirmedReceivedSequenceNumber; // for limit inbound speed StreamStatus m_Status; + bool m_IsIncoming; bool m_IsAckSendScheduled; bool m_IsNAcked; bool m_IsFirstACK; diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 6c2c52a7..edf96c31 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2022, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,9 +8,12 @@ #include #include "I2PEndian.h" +#include "Crypto.h" #include "Log.h" #include "RouterContext.h" #include "I2NPProtocol.h" +#include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "Tunnel.h" #include "Transports.h" #include "TransitTunnel.h" @@ -59,8 +62,7 @@ namespace tunnel auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); - i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); - m_TunnelDataMsgs.clear (); + i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear } } @@ -119,5 +121,334 @@ namespace tunnel return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } + + void TransitTunnels::Start () + { + } + + void TransitTunnels::Stop () + { + m_TransitTunnels.clear (); + } + + void TransitTunnels::HandleShortTransitTunnelBuildMsg (std::shared_ptr&& msg) + { + if (!msg) return; + uint8_t * buf = msg->GetPayload(); + size_t len = msg->GetPayloadLength(); + int num = buf[0]; + LogPrint (eLogDebug, "TransitTunnel: ShortTunnelBuild ", num, " records"); + if (num > i2p::tunnel::MAX_NUM_RECORDS) + { + LogPrint (eLogError, "TransitTunnel: Too many records in ShortTunnelBuild message ", num); + return; + } + if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "TransitTunnel: ShortTunnelBuild message of ", num, " records is too short ", len); + return; + } + const uint8_t * record = buf + 1; + for (int i = 0; i < num; i++) + { + if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "TransitTunnel: Short request record ", i, " is ours"); + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "TransitTunnel: Can't decrypt short request record ", i); + return; + } + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "TransitTunnel: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); + return; + } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305 + i2p::crypto::AESKey layerKey, ivKey; // AES + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); + memcpy (replyKey, noiseState.m_CK + 32, 32); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + { + if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours + { + LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in short request record"); + return; + } + memcpy (ivKey, noiseState.m_CK , 32); + } + + // check if we accept this tunnel + std::shared_ptr transitTunnel; + uint8_t retCode = 0; + if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL) + retCode = 30; + if (!retCode) + { + // create new transit tunnel + transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + + // encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "TransitTunnel: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // send reply + auto onDrop = [transitTunnel]() + { + if (transitTunnel) + { + auto t = transitTunnel->GetCreationTime (); + if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT) + // make transit tunnel expired + transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT); + } + }; + if (isEndpoint) + { + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (transitTunnel) replyMsg->onDrop = onDrop; + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + { + tunnel->SendTunnelDataMsg (replyMsg); + tunnel->FlushTunnelDataMsgs (); + } + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } + } + else + { + auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (transitTunnel) msg->onDrop = onDrop; + i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg); + } + return; + } + record += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + } + + bool TransitTunnels::HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + { + for (int i = 0; i < num; i++) + { + uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; + if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "TransitTunnel: Build request record ", i, " is ours"); + if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "TransitTunnel: Failed to decrypt tunnel build record"); + return false; + } + if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours + !(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint + { + LogPrint (eLogWarning, "TransitTunnel: Next ident is ours in tunnel build record"); + return false; + } + uint8_t retCode = 0; + // decide if we should accept tunnel + bool accept = i2p::context.AcceptsTunnels (); + if (accept) + { + auto congestionLevel = i2p::context.GetCongestionLevel (false); + if (congestionLevel >= CONGESTION_LEVEL_MEDIUM) + { + if (congestionLevel < CONGESTION_LEVEL_FULL) + { + // random reject depending on congestion level + int level = i2p::tunnel::tunnels.GetRng ()() % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM; + if (congestionLevel > level) + accept = false; + } + else + accept = false; + } + } + // replace record to reply + if (accept) + { + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + if (!AddTransitTunnel (transitTunnel)) + retCode = 30; + } + else + retCode = 30; // always reject with bandwidth reason (30) + + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; + // encrypt reply + i2p::crypto::CBCEncryption encryption; + for (int j = 0; j < num; j++) + { + uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "TransitTunnel: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + } + } + return true; + } + } + return false; + } + + void TransitTunnels::HandleVariableTransitTunnelBuildMsg (std::shared_ptr&& msg) + { + if (!msg) return; + uint8_t * buf = msg->GetPayload(); + size_t len = msg->GetPayloadLength(); + int num = buf[0]; + LogPrint (eLogDebug, "TransitTunnel: VariableTunnelBuild ", num, " records"); + if (num > i2p::tunnel::MAX_NUM_RECORDS) + { + LogPrint (eLogError, "TransitTunnle: Too many records in VaribleTunnelBuild message ", num); + return; + } + if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "TransitTunnel: VaribleTunnelBuild message of ", num, " records is too short ", len); + return; + } + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) + { + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } + + bool TransitTunnels::AddTransitTunnel (std::shared_ptr tunnel) + { + if (tunnels.AddTunnel (tunnel)) + m_TransitTunnels.push_back (tunnel); + else + { + LogPrint (eLogError, "TransitTunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); + return false; + } + return true; + } + + void TransitTunnels::ManageTransitTunnels (uint64_t ts) + { + for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) + { + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) + { + LogPrint (eLogDebug, "TransitTunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); + tunnels.RemoveTunnel (tunnel->GetTunnelID ()); + it = m_TransitTunnels.erase (it); + } + else + { + tunnel->Cleanup (); + it++; + } + } + } + + int TransitTunnels::GetTransitTunnelsExpirationTimeout () + { + int timeout = 0; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + // TODO: possible race condition with I2PControl + for (const auto& it : m_TransitTunnels) + { + int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; + if (t > timeout) timeout = t; + } + return timeout; + } } } diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index f83007a9..fc8676d0 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,7 +10,7 @@ #define TRANSIT_TUNNEL_H__ #include -#include +#include #include #include #include "Crypto.h" @@ -61,7 +61,7 @@ namespace tunnel private: size_t m_NumTransmittedBytes; - std::vector > m_TunnelDataMsgs; + std::list > m_TunnelDataMsgs; }; class TransitTunnelGateway: public TransitTunnel @@ -108,6 +108,35 @@ namespace tunnel const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint); + + class TransitTunnels + { + public: + + void Start (); + void Stop (); + void ManageTransitTunnels (uint64_t ts); + + size_t GetNumTransitTunnels () const { return m_TransitTunnels.size (); } + int GetTransitTunnelsExpirationTimeout (); + + void HandleShortTransitTunnelBuildMsg (std::shared_ptr&& msg); + void HandleVariableTransitTunnelBuildMsg (std::shared_ptr&& msg); + + private: + + bool AddTransitTunnel (std::shared_ptr tunnel); + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); + + private: + + std::list > m_TransitTunnels; + + public: + + // for HTTP only + auto& GetTransitTunnels () const { return m_TransitTunnels; }; + }; } } diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index c6bf0de3..6c878d11 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -144,8 +144,12 @@ namespace transport void SetLastActivityTimestamp (uint64_t ts) { m_LastActivityTimestamp = ts; }; virtual uint32_t GetRelayTag () const { return 0; }; - virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; - virtual void SendI2NPMessages (const std::vector >& msgs) = 0; + virtual void SendLocalRouterInfo (bool update = false) + { + std::list > msgs{ CreateDatabaseStoreMsg () }; + SendI2NPMessages (msgs); + }; + virtual void SendI2NPMessages (std::list >& msgs) = 0; virtual bool IsEstablished () const = 0; private: diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 34bc6142..b07b1b0b 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -25,7 +25,7 @@ namespace transport { template EphemeralKeysSupplier::EphemeralKeysSupplier (int size): - m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) + m_QueueSize (size), m_IsRunning (false) { } @@ -39,7 +39,7 @@ namespace transport void EphemeralKeysSupplier::Start () { m_IsRunning = true; - m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); + m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier::Run, this))); } template @@ -53,8 +53,7 @@ namespace transport if (m_Thread) { m_Thread->join (); - delete m_Thread; - m_Thread = 0; + m_Thread = nullptr; } } @@ -66,18 +65,19 @@ namespace transport while (m_IsRunning) { int num, total = 0; - while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10) + while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize) { CreateEphemeralKeys (num); total += num; } - if (total >= 10) + if (total > m_QueueSize) { LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else { + m_KeysPool.CleanUpMt (); std::unique_lock l(m_AcquiredMutex); if (!m_IsRunning) break; m_Acquired.wait (l); // wait for element gets acquired @@ -92,7 +92,7 @@ namespace transport { for (int i = 0; i < num; i++) { - auto pair = std::make_shared (); + auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); @@ -114,7 +114,7 @@ namespace transport } } // queue is empty, create new - auto pair = std::make_shared (); + auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); return pair; } @@ -124,12 +124,12 @@ namespace transport { if (pair) { - std::unique_lockl(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else - LogPrint(eLogError, "Transports: Return null DHKeys"); + LogPrint(eLogError, "Transports: Return null keys"); } void Peer::UpdateParams (std::shared_ptr router) @@ -149,7 +149,7 @@ namespace transport m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), - m_X25519KeysPairSupplier (15), // 15 pre-generated keys + m_X25519KeysPairSupplier (NUM_X25519_PRE_GENERATED_KEYS), m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), @@ -450,15 +450,25 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { if (m_IsOnline) - SendMessages (ident, std::vector > {msg }); + SendMessages (ident, { msg }); } - void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) + void Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >& msgs) { - m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); + std::list > msgs1; + msgs.swap (msgs1); + SendMessages (ident, std::move (msgs1)); } - void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) + void Transports::SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs) + { + m_Service->post ([this, ident, msgs = std::move(msgs)] () mutable + { + PostMessages (ident, msgs); + }); + } + + void Transports::PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { @@ -470,8 +480,13 @@ namespace transport } if(RoutesRestricted() && !IsRestrictedPeer(ident)) return; std::shared_ptr peer; - auto it = m_Peers.find (ident); - if (it == m_Peers.end ()) + { + std::lock_guard l(m_PeersMutex); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) + peer = it->second; + } + if (!peer) { // check if not banned if (i2p::data::IsRouterBanned (ident)) return; // don't create peer to unreachable router @@ -481,10 +496,10 @@ namespace transport { auto r = netdb.FindRouter (ident); if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - peer = std::make_shared(r, ts); - std::unique_lock l(m_PeersMutex); + + peer = std::make_shared(r, i2p::util::GetSecondsSinceEpoch ()); + { + std::lock_guard l(m_PeersMutex); peer = m_Peers.emplace (ident, peer).first->second; } if (peer) @@ -496,8 +511,6 @@ namespace transport } if (!connected) return; } - else - peer = it->second; if (!peer) return; if (peer->IsConnected ()) @@ -512,22 +525,27 @@ namespace transport if (i2p::data::IsRouterBanned (ident)) { LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return; } } - for (auto& it1: msgs) - if (sz > MAX_NUM_DELAYED_MESSAGES/2 && it1->onDrop) - it1->Drop (); // drop earlier because we can handle it - else - peer->delayedMessages.push_back (it1); + if (sz > MAX_NUM_DELAYED_MESSAGES/2) + { + for (auto& it1: msgs) + if (it1->onDrop) + it1->Drop (); // drop earlier because we can handle it + else + peer->delayedMessages.push_back (it1); + } + else + peer->delayedMessages.splice (peer->delayedMessages.end (), msgs); } else { LogPrint (eLogWarning, "Transports: Delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); } } @@ -602,7 +620,7 @@ namespace transport if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them peer->Done (); - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -610,7 +628,7 @@ namespace transport { LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); peer->Done (); - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -706,23 +724,29 @@ namespace transport void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { - auto it = m_Peers.find (ident); - if (it != m_Peers.end ()) + std::shared_ptr peer; { - if (r) - { - LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); - it->second->SetRouter (r); - if (!it->second->IsConnected ()) - ConnectToPeer (ident, it->second); - } - else + std::lock_guard l(m_PeersMutex); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) { - LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages"); - std::unique_lock l(m_PeersMutex); - m_Peers.erase (it); - } + if (r) + peer = it->second; + else + m_Peers.erase (it); + } + } + + if (peer && !peer->router && r) + { + LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); + peer->SetRouter (r); + if (!peer->IsConnected ()) + ConnectToPeer (ident, peer); } + else if (!r) + LogPrint (eLogInfo, "Transports: RouterInfo not found, failed to send messages"); + } void Transports::DetectExternalIP () @@ -865,7 +889,7 @@ namespace transport if (it->second->delayedMessages.size () > 0) { // check if first message is our DatabaseStore (publishing) - auto firstMsg = peer->delayedMessages[0]; + auto firstMsg = peer->delayedMessages.front (); if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already @@ -875,8 +899,7 @@ namespace transport else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds peer->sessions.push_back (session); - session->SendI2NPMessages (peer->delayedMessages); - peer->delayedMessages.clear (); + session->SendI2NPMessages (peer->delayedMessages); // send and clear } else // incoming connection or peer test { @@ -887,14 +910,17 @@ namespace transport return; } if (!session->IsOutgoing ()) // incoming - session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore + { + std::list > msgs{ CreateDatabaseStoreMsg () }; + session->SendI2NPMessages (msgs); // send DatabaseStore + } auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed if (r) r->GetProfile ()->Connected (); auto ts = i2p::util::GetSecondsSinceEpoch (); auto peer = std::make_shared(r, ts); peer->sessions.push_back (session); peer->router = nullptr; - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.emplace (ident, peer); } }); @@ -923,7 +949,7 @@ namespace transport } else { - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); m_Peers.erase (it); } } @@ -933,9 +959,13 @@ namespace transport bool Transports::IsConnected (const i2p::data::IdentHash& ident) const { - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); +#if __cplusplus >= 202002L // C++20 + return m_Peers.contains (ident); +#else auto it = m_Peers.find (ident); return it != m_Peers.end (); +#endif } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) @@ -959,7 +989,7 @@ namespace transport auto profile = i2p::data::GetRouterProfile (it->first); if (profile) profile->Unreachable (); } */ - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); it = m_Peers.erase (it); } else @@ -1009,7 +1039,7 @@ namespace transport { uint16_t inds[3]; RAND_bytes ((uint8_t *)inds, sizeof (inds)); - std::unique_lock l(m_PeersMutex); + std::lock_guard l(m_PeersMutex); auto count = m_Peers.size (); if(count == 0) return nullptr; inds[0] %= count; diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 70273094..8271108d 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -26,6 +26,7 @@ #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" +#include "util.h" namespace i2p { @@ -53,9 +54,10 @@ namespace transport const int m_QueueSize; std::queue > m_Queue; + i2p::util::MemoryPoolMt m_KeysPool; bool m_IsRunning; - std::thread * m_Thread; + std::unique_ptr m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; @@ -71,7 +73,7 @@ namespace transport std::shared_ptr router; std::list > sessions; uint64_t creationTime, nextRouterInfoUpdateTime, lastSelectionTime; - std::vector > delayedMessages; + std::list > delayedMessages; std::vector priority; bool isHighBandwidth, isEligible; @@ -108,7 +110,8 @@ namespace transport const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds const int MAX_NUM_DELAYED_MESSAGES = 150; const int CHECK_PROFILE_NUM_DELAYED_MESSAGES = 15; // check profile after - + const int NUM_X25519_PRE_GENERATED_KEYS = 25; // pre-generated x25519 keys pairs + const int TRAFFIC_SAMPLE_COUNT = 301; // seconds struct TrafficSample @@ -141,7 +144,8 @@ namespace transport void ReuseX25519KeysPair (std::shared_ptr pair); void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); - void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); + void SendMessages (const i2p::data::IdentHash& ident, std::list >& msgs); + void SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); @@ -185,7 +189,7 @@ namespace transport void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); - void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); + void PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs); bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr peer); void SetPriority (std::shared_ptr peer) const; void HandlePeerCleanupTimer (const boost::system::error_code& ecode); diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 1e2a75d9..d809e48a 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -130,8 +130,19 @@ namespace tunnel bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { - LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - + int num = msg[0]; + LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", num, " records."); + if (num > MAX_NUM_RECORDS) + { + LogPrint (eLogError, "Tunnel: Too many records in TunnelBuildResponse", num); + return false; + } + if (len < num*m_Config->GetRecordSize () + 1) + { + LogPrint (eLogError, "Tunnel: TunnelBuildResponse of ", num, " records is too short ", len); + return false; + } + TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { @@ -152,7 +163,7 @@ namespace tunnel while (hop1) { auto idx = hop1->recordIndex; - if (idx >= 0 && idx < msg[0]) + if (idx >= 0 && idx < num) hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range"); @@ -368,6 +379,17 @@ namespace tunnel return nullptr; } + bool Tunnels::AddTunnel (std::shared_ptr tunnel) + { + if (!tunnel) return false; + return m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second; + } + + void Tunnels::RemoveTunnel (uint32_t tunnelID) + { + m_Tunnels.erase (tunnelID); + } + std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); @@ -455,26 +477,16 @@ namespace tunnel } } - bool Tunnels::AddTransitTunnel (std::shared_ptr tunnel) - { - if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) - m_TransitTunnels.push_back (tunnel); - else - { - LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); - return false; - } - return true; - } - void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); + m_TransitTunnels.Start (); } void Tunnels::Stop () { + m_TransitTunnels.Stop (); m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) @@ -533,14 +545,22 @@ namespace tunnel break; } - case eI2NPVariableTunnelBuild: - case eI2NPVariableTunnelBuildReply: case eI2NPShortTunnelBuild: + HandleShortTunnelBuildMsg (msg); + break; + case eI2NPVariableTunnelBuild: + HandleVariableTunnelBuildMsg (msg); + break; case eI2NPShortTunnelBuildReply: + HandleTunnelBuildReplyMsg (msg, true); + break; + case eI2NPVariableTunnelBuildReply: + HandleTunnelBuildReplyMsg (msg, false); + break; case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - HandleTunnelBuildI2NPMessage (msg); - break; + LogPrint (eLogWarning, "Tunnel: TunnelBuild is too old for ECIES router"); + break; default: LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } @@ -613,12 +633,84 @@ namespace tunnel tunnel->SendTunnelDataMsg (msg); } + void Tunnels::HandleShortTunnelBuildMsg (std::shared_ptr msg) + { + if (!msg) return; + auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + return; + } + else + m_TransitTunnels.HandleShortTransitTunnelBuildMsg (std::move (msg)); + } + + void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr msg) + { + auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + } + else + m_TransitTunnels.HandleVariableTransitTunnelBuildMsg (std::move (msg)); + } + + void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort) + { + auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID + if (tunnel) + { + // reply for outbound tunnel + LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) + { + LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (eTunnelStateEstablished); + AddOutboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (eTunnelStateBuildFailed); + } + } + else + LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found"); + + } + void Tunnels::ManageTunnels (uint64_t ts) { ManagePendingTunnels (ts); ManageInboundTunnels (ts); ManageOutboundTunnels (ts); - ManageTransitTunnels (ts); + m_TransitTunnels.ManageTransitTunnels (ts); } void Tunnels::ManagePendingTunnels (uint64_t ts) @@ -745,7 +837,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); - m_Tunnels.erase (tunnel->GetTunnelID ()); + RemoveTunnel (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); } else @@ -807,26 +899,6 @@ namespace tunnel } } - void Tunnels::ManageTransitTunnels (uint64_t ts) - { - for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) - { - auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || - ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) - { - LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); - m_Tunnels.erase (tunnel->GetTunnelID ()); - it = m_TransitTunnels.erase (it); - } - else - { - tunnel->Cleanup (); - it++; - } - } - } - void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); @@ -900,7 +972,7 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { - if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) + if (AddTunnel (newTunnel)) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); @@ -930,7 +1002,7 @@ namespace tunnel inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); - m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; + AddTunnel (inboundTunnel); return inboundTunnel; } @@ -964,21 +1036,12 @@ namespace tunnel int Tunnels::GetTransitTunnelsExpirationTimeout () { - int timeout = 0; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - // TODO: possible race condition with I2PControl - for (const auto& it : m_TransitTunnels) - { - int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; - if (t > timeout) timeout = t; - } - return timeout; + return m_TransitTunnels.GetTransitTunnelsExpirationTimeout (); } size_t Tunnels::CountTransitTunnels() const { - // TODO: locking - return m_TransitTunnels.size(); + return m_TransitTunnels.GetNumTransitTunnels (); } size_t Tunnels::CountInboundTunnels() const diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 02bfb374..fcccd236 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -223,8 +223,9 @@ namespace tunnel std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); + bool AddTunnel (std::shared_ptr tunnel); + void RemoveTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); - bool AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); @@ -243,7 +244,7 @@ namespace tunnel void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels); uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; - int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.size() / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } + int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; } std::mt19937& GetRng () { return m_Rng; }; @@ -257,12 +258,14 @@ namespace tunnel std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); - + void HandleShortTunnelBuildMsg (std::shared_ptr msg); + void HandleVariableTunnelBuildMsg (std::shared_ptr msg); + void HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort); + void Run (); void ManageTunnels (uint64_t ts); void ManageOutboundTunnels (uint64_t ts); void ManageInboundTunnels (uint64_t ts); - void ManageTransitTunnels (uint64_t ts); void ManagePendingTunnels (uint64_t ts); template void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); @@ -297,7 +300,6 @@ namespace tunnel std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; - std::list > m_TransitTunnels; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id std::mutex m_PoolsMutex; std::list> m_Pools; @@ -311,13 +313,14 @@ namespace tunnel double m_TunnelCreationSuccessRate; int m_TunnelCreationAttemptsNum; std::mt19937 m_Rng; - + TransitTunnels m_TransitTunnels; + public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; - const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; + auto& GetTransitTunnels () const { return m_TransitTunnels.GetTransitTunnels (); }; size_t CountTransitTunnels() const; size_t CountInboundTunnels() const; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 9dcf2c02..718a6fdb 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -181,6 +181,8 @@ namespace tunnel return peers; } + size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; }; + protected: // this constructor can't be called from outside diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 85ff224e..78a63fc4 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -221,7 +221,7 @@ namespace tunnel void TunnelGateway::SendBuffer () { m_Buffer.CompleteCurrentTunnelDataMessage (); - std::vector > newTunnelMsgs; + std::list > newTunnelMsgs; const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { @@ -234,7 +234,7 @@ namespace tunnel m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } m_Buffer.ClearTunnelDataMsgs (); - i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs); + i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), std::move (newTunnelMsgs)); } } } diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index d1ed9992..47cecbbe 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2023, The PurpleI2P Project +* Copyright (c) 2013-2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -171,6 +171,14 @@ namespace util } } + void RunnableService::SetName (std::string_view name) + { + if (name.length() < 16) + m_Name = name; + else + m_Name = name.substr(0,15); + } + void SetThreadName (const char *name) { #if defined(__APPLE__) # if (!defined(MAC_OS_X_VERSION_10_6) || \ diff --git a/libi2pd/util.h b/libi2pd/util.h index e39a9259..48bcb801 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -137,8 +137,8 @@ namespace util std::lock_guard l(m_Mutex); for (size_t i = 0; i < num; i++) this->Release (arr[i]); - } - + } + templateclass C, typename... R> void ReleaseMt(const C& c) { @@ -146,7 +146,7 @@ namespace util for (auto& it: c) this->Release (it); } - + template std::shared_ptr AcquireSharedMt (TArgs&&... args) { @@ -183,6 +183,8 @@ namespace util void StartIOService (); void StopIOService (); + void SetName (std::string_view name); + private: void Run (); diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 14599cf7..c9452cc6 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -305,7 +305,7 @@ namespace client identHash = hash; } - AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), + AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr), m_IsEnabled (true) { @@ -344,20 +344,28 @@ namespace client delete m_SubscriptionsUpdateTimer; m_SubscriptionsUpdateTimer = nullptr; } - if (m_IsDownloading) + bool isDownloading = m_Downloading.valid (); + if (isDownloading) { - LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); - for (int i = 0; i < 30; i++) - { - if (!m_IsDownloading) + if (m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + isDownloading = false; + else + { + LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); + for (int i = 0; i < 30; i++) { - LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); - break; + if (m_Downloading.wait_for(std::chrono::seconds(1)) == std::future_status::ready) // wait for 1 seconds + { + isDownloading = false; + LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); + break; + } } - std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds - } - LogPrint (eLogError, "Addressbook: Subscription download timeout"); - m_IsDownloading = false; + } + if (!isDownloading) + m_Downloading.get (); + else + LogPrint (eLogError, "Addressbook: Subscription download timeout"); } if (m_Storage) { @@ -582,16 +590,15 @@ namespace client } else { - LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config file"); + LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config"); // using config file items std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); for (const auto& s: subsList) - { - m_Subscriptions.push_back (std::make_shared (*this, s)); - } + if (!s.empty ()) + m_Subscriptions.push_back (std::make_shared (*this, s)); LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } } @@ -645,7 +652,6 @@ namespace client void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { - m_IsDownloading = false; m_NumRetries++; int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT) @@ -700,7 +706,13 @@ namespace client LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); return; } - if (!m_IsDownloading && dest->IsReady ()) + bool isDownloading = m_Downloading.valid (); + if (isDownloading && m_Downloading.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active? + { + m_Downloading.get (); + isDownloading = false; + } + if (!isDownloading && dest->IsReady ()) { if (!m_IsLoaded) { @@ -709,17 +721,15 @@ namespace client std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); - m_IsDownloading = true; - std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); - load_hosts.detach(); // TODO: use join + m_Downloading = std::async (std::launch::async, + std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); - m_IsDownloading = true; - std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); - load_hosts.detach(); // TODO: use join + m_Downloading = std::async (std::launch::async, + std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); } } else @@ -823,7 +833,7 @@ namespace client } } - AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): + AddressBookSubscription::AddressBookSubscription (AddressBook& book, std::string_view link): m_Book (book), m_Link (link) { } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index fc4f19a7..553ae51b 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -11,10 +11,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include "Base.h" @@ -124,7 +126,8 @@ namespace client std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; - volatile bool m_IsLoaded, m_IsDownloading; + volatile bool m_IsLoaded; + std::future m_Downloading; int m_NumRetries; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet @@ -136,7 +139,7 @@ namespace client { public: - AddressBookSubscription (AddressBook& book, const std::string& link); + AddressBookSubscription (AddressBook& book, std::string_view link); void CheckUpdates (); private: diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index cf72d204..aa49f83e 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -421,7 +421,9 @@ namespace client { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, { I2CP_PARAM_LEASESET_TYPE, "3" }, - { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" } + { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" }, + { I2CP_PARAM_OUTBOUND_NICKNAME, "SharedDest" }, + { I2CP_PARAM_STREAMING_PROFILE, "2" } }; m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA @@ -473,8 +475,9 @@ namespace client options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED, DEFAULT_MAX_OUTBOUND_SPEED); options[I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED, DEFAULT_MAX_INBOUND_SPEED); + options[I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); - options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); + options[I2CP_PARAM_STREAMING_PROFILE] = GetI2CPOption(section, I2CP_PARAM_STREAMING_PROFILE, DEFAULT_STREAMING_PROFILE); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "4" : "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; @@ -595,6 +598,11 @@ namespace client std::map options; ReadI2CPOptions (section, false, options); + // Set I2CP name if not set + auto itopt = options.find (I2CP_PARAM_OUTBOUND_NICKNAME); + if (itopt == options.end ()) + options[I2CP_PARAM_OUTBOUND_NICKNAME] = name; + std::shared_ptr localDestination = nullptr; if (keys.length () > 0) { @@ -667,7 +675,7 @@ namespace client std::string outproxy = section.second.get("outproxy", ""); bool addresshelper = section.second.get("addresshelper", true); bool senduseragent = section.second.get("senduseragent", false); - auto tun = std::make_shared(name, address, port, + auto tun = std::make_shared(name, address, port, outproxy, addresshelper, senduseragent, localDestination); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); @@ -749,6 +757,11 @@ namespace client std::map options; ReadI2CPOptions (section, true, options); + // Set I2CP name if not set + auto itopt = options.find (I2CP_PARAM_INBOUND_NICKNAME); + if (itopt == options.end ()) + options[I2CP_PARAM_INBOUND_NICKNAME] = name; + std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); if (it != destinations.end ()) @@ -896,6 +909,7 @@ namespace client { std::map params; ReadI2CPOptionsFromConfig ("httpproxy.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "HTTPProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } @@ -904,7 +918,7 @@ namespace client } try { - m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, + m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, httpSendUserAgent, localDestination); m_HttpProxy->Start(); } @@ -944,6 +958,7 @@ namespace client { std::map params; ReadI2CPOptionsFromConfig ("socksproxy.", params); + params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SOCKSProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index de319f5d..6353a683 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,10 +49,6 @@ set(test-gost-sig_SRCS test-gost-sig.cpp ) -set(test-x25519_SRCS - test-x25519.cpp -) - set(test-aeadchacha20poly1305_SRCS test-aeadchacha20poly1305.cpp ) @@ -77,7 +73,6 @@ add_executable(test-http-url ${test-http-url_SRCS}) add_executable(test-base-64 ${test-base-64_SRCS}) add_executable(test-gost ${test-gost_SRCS}) add_executable(test-gost-sig ${test-gost-sig_SRCS}) -add_executable(test-x25519 ${test-x25519_SRCS}) add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) add_executable(test-blinding ${test-blinding_SRCS}) add_executable(test-elligator ${test-elligator_SRCS}) @@ -102,7 +97,6 @@ target_link_libraries(test-http-url ${LIBS}) target_link_libraries(test-base-64 ${LIBS}) target_link_libraries(test-gost ${LIBS}) target_link_libraries(test-gost-sig ${LIBS}) -target_link_libraries(test-x25519 ${LIBS}) target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) target_link_libraries(test-blinding ${LIBS}) target_link_libraries(test-elligator ${LIBS}) @@ -116,7 +110,6 @@ add_test(test-http-url ${TEST_PATH}/test-http-url) add_test(test-base-64 ${TEST_PATH}/test-base-64) add_test(test-gost ${TEST_PATH}/test-gost) add_test(test-gost-sig ${TEST_PATH}/test-gost-sig) -add_test(test-x25519 ${TEST_PATH}/test-x25519) add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) add_test(test-blinding ${TEST_PATH}/test-blinding) add_test(test-elligator ${TEST_PATH}/test-elligator) diff --git a/tests/Makefile b/tests/Makefile index 798fab42..51a11dfe 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -7,7 +7,7 @@ LIBI2PD = ../libi2pd.a TESTS = \ test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \ - test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding \ + test-gost test-gost-sig test-base-64 test-aeadchacha20poly1305 test-blinding \ test-elligator test-eddsa ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) @@ -44,9 +44,6 @@ test-gost: test-gost.cpp $(LIBI2PD) test-gost-sig: test-gost-sig.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-x25519: test-x25519.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) - test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp deleted file mode 100644 index a1f3f424..00000000 --- a/tests/test-x25519.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include - -#include "Ed25519.h" - -const uint8_t k[32] = -{ - 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, - 0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, - 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4 -}; - -const uint8_t u[32] = -{ - 0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, - 0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, - 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c -}; - -uint8_t p[32] = -{ - 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, - 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, - 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52 -}; - -int main () -{ -#if !OPENSSL_X25519 -// we test it for openssl < 1.1.0 - uint8_t buf[32]; - BN_CTX * ctx = BN_CTX_new (); - i2p::crypto::GetEd25519 ()->ScalarMul (u, k, buf, ctx); - BN_CTX_free (ctx); - assert(memcmp (buf, p, 32) == 0); -#endif -}