diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 7885578e..b1f698c1 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -122,7 +122,7 @@ namespace transport err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); #endif m_upnpUrlsInitialized=err!=0; - if (err == UPNP_IGD_VALID_CONNECTED) + if (err == UPNP_IGD_VALID_CONNECTED || err == UPNP_IGD_VALID_NOT_CONNECTED) { #if (MINIUPNPC_API_VERSION < 18) err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index d1eb1c39..9414962d 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -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 * @@ -25,6 +25,7 @@ #include "RouterContext.h" #include "ClientContext.h" #include "Transports.h" +#include "util.h" void handle_signal(int sig) { @@ -220,6 +221,7 @@ namespace i2p void DaemonLinux::run () { + i2p::util::SetThreadName ("Daemon"); while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 2f9677c1..8ce290f1 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -997,7 +997,7 @@ namespace crypto } else { -#if defined(LIBRESSL_VERSION_NUMBER) +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL std::vector m(msgLen + 16); if (msg == buf) { diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 1846f08e..be674d95 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -11,6 +11,7 @@ #include #include +#include #include "Identity.h" namespace i2p @@ -67,6 +68,11 @@ namespace data bool IsUseful() const; bool IsDuplicated () const { return m_IsDuplicated; }; + + const boost::asio::ip::udp::endpoint& GetLastEndpoint () const { return m_LastEndpoint; } + void SetLastEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_LastEndpoint = ep; } + bool HasLastEndpoint (bool v4) const { return !m_LastEndpoint.address ().is_unspecified () && m_LastEndpoint.port () && + ((v4 && m_LastEndpoint.address ().is_v4 ()) || (!v4 && m_LastEndpoint.address ().is_v6 ())); } private: @@ -90,6 +96,8 @@ namespace data uint32_t m_NumTimesRejected; bool m_HasConnected; // successful trusted(incoming or NTCP2) connection bool m_IsDuplicated; + // connectivity + boost::asio::ip::udp::endpoint m_LastEndpoint; // SSU2 for non-published addresses }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index e96a7233..01a3bb6c 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -216,15 +216,16 @@ namespace transport return ep.port (); } - bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep) + bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max) { if (!ep.port () || ep.address ().is_unspecified ()) return false; + std::lock_guard l(m_ConnectedRecentlyMutex); auto it = m_ConnectedRecently.find (ep); if (it != m_ConnectedRecently.end ()) { - if (i2p::util::GetSecondsSinceEpoch () <= it->second + SSU2_HOLE_PUNCH_EXPIRATION) + if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION)) return true; - else + else if (max) m_ConnectedRecently.erase (it); } return false; @@ -233,7 +234,8 @@ namespace transport void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts) { if (!ep.port () || ep.address ().is_unspecified () || - i2p::util::GetSecondsSinceEpoch () > ts + SSU2_HOLE_PUNCH_EXPIRATION) return; + i2p::util::GetSecondsSinceEpoch () > ts + SSU2_MAX_HOLE_PUNCH_EXPIRATION) return; + std::lock_guard l(m_ConnectedRecentlyMutex); auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts); if (!added && ts > it->second) it->second = ts; // renew timestamp of existing endpoint @@ -833,6 +835,29 @@ namespace transport } } + bool SSU2Server::CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest) + { + auto s = FindPendingOutgoingSession (ep); + if (s) + { + if (peerTest) + { + // if peer test requested add it to the list for pending session + auto onEstablished = s->GetOnEstablished (); + if (onEstablished) + s->SetOnEstablished ([s, onEstablished]() + { + onEstablished (); + s->SendPeerTest (); + }); + else + s->SetOnEstablished ([s]() { s->SendPeerTest (); }); + } + return true; + } + return false; + } + bool SSU2Server::CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest) { @@ -852,34 +877,28 @@ namespace transport if (isValidEndpoint) { if (i2p::transport::transports.IsInReservedRange(address->host)) return false; - auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port)); - if (s) - { - if (peerTest) - { - // if peer test requested add it to the list for pending session - auto onEstablished = s->GetOnEstablished (); - if (onEstablished) - s->SetOnEstablished ([s, onEstablished]() - { - onEstablished (); - s->SendPeerTest (); - }); - else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); - } - return false; - } + if (CheckPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port), peerTest)) return false; } auto session = std::make_shared (*this, router, address); + if (!isValidEndpoint && router->GetProfile ()->HasLastEndpoint (address->IsV4 ())) + { + // router doesn't publish endpoint, but we connected before and hole punch might be alive + auto ep = router->GetProfile ()->GetLastEndpoint (); + if (IsConnectedRecently (ep, false)) + { + if (CheckPendingOutgoingSession (ep, peerTest)) return false; + session->SetRemoteEndpoint (ep); + isValidEndpoint = true; + } + } if (peerTest) session->SetOnEstablished ([session]() {session->SendPeerTest (); }); - if (address->UsesIntroducer ()) - GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session)); - else if (isValidEndpoint) // we can't connect without endpoint + if (isValidEndpoint) // we know endpoint GetService ().post ([session]() { session->Connect (); }); + else if (address->UsesIntroducer ()) // we don't know endpoint yet + GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session)); else return false; } @@ -1129,7 +1148,7 @@ namespace transport for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); ) { - if (ts > it->second + SSU2_HOLE_PUNCH_EXPIRATION) + if (ts > it->second + SSU2_MAX_HOLE_PUNCH_EXPIRATION) it = m_ConnectedRecently.erase (it); else it++; diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h index 426c6a10..0433033d 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -42,7 +42,8 @@ namespace transport const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds - const int SSU2_HOLE_PUNCH_EXPIRATION = 150; // in seconds + const int SSU2_MIN_HOLE_PUNCH_EXPIRATION = 45; // in seconds + const int SSU2_MAX_HOLE_PUNCH_EXPIRATION = 181; // in seconds const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 64; class SSU2Server: private i2p::util::RunnableServiceWithWork @@ -77,7 +78,7 @@ namespace transport bool UsesProxy () const { return m_IsThroughProxy; }; bool IsSupported (const boost::asio::ip::address& addr) const; uint16_t GetPort (bool v4) const; - bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep); + bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true); void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts); std::mt19937& GetRng () { return m_Rng; } bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; } @@ -147,6 +148,7 @@ namespace transport void ScheduleResend (bool more); void HandleResendTimer (const boost::system::error_code& ecode); + bool CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest); void ConnectThroughIntroducer (std::shared_ptr session); std::vector > FindIntroducers (int maxNumIntroducers, bool v4, const std::unordered_set& excluded); @@ -193,6 +195,7 @@ namespace transport std::shared_ptr m_PendingTimeOffsetFrom; std::mt19937 m_Rng; std::map m_ConnectedRecently; // endpoint -> last activity time in seconds + mutable std::mutex m_ConnectedRecentlyMutex; std::unordered_map, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp) std::list m_ReceivedPacketsQueue; mutable std::mutex m_ReceivedPacketsQueueMutex; diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 17d2c1da..20f21ce5 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -226,6 +226,13 @@ namespace transport if (m_Server.AddPendingOutgoingSession (shared_from_this ())) { m_Server.RemoveSession (GetConnID ()); + // update endpoint in profile because we know it now + auto identity = GetRemoteIdentity (); + if (identity) + { + auto profile = i2p::data::GetRouterProfile (identity->GetIdentHash ()); + if (profile) profile->SetLastEndpoint (m_RemoteEndpoint); + } // connect LogPrint (eLogDebug, "SSU2: Connecting after introduction to ", GetIdentHashBase64()); Connect (); @@ -1169,6 +1176,8 @@ namespace transport " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); return false; } + if (!m_Address->published) + ri->GetProfile ()->SetLastEndpoint (m_RemoteEndpoint); SetRemoteIdentity (ri->GetRouterIdentity ()); AdjustMaxPayloadSize (); m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 549efb63..34bc6142 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -672,6 +672,31 @@ namespace transport if (transport & compatibleTransports) peer->priority.push_back (transport); } + if (peer->priority.empty ()) + { + // try recently connected SSU2 if any + auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & + peer->router->GetCompatibleTransports (false); + if (supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) + { + auto ep = peer->router->GetProfile ()->GetLastEndpoint (); + if (!ep.address ().is_unspecified () && ep.port ()) + { + if (ep.address ().is_v4 ()) + { + if ((supportedTransports & i2p::data::RouterInfo::eSSU2V4) && + m_SSU2Server->IsConnectedRecently (ep, false)) + peer->priority.push_back (i2p::data::RouterInfo::eSSU2V4); + } + else if (ep.address ().is_v6 ()) + { + if ((supportedTransports & i2p::data::RouterInfo::eSSU2V6) && + m_SSU2Server->IsConnectedRecently (ep)) + peer->priority.push_back (i2p::data::RouterInfo::eSSU2V6); + } + } + } + } } void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident)