diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 05766c3c..25dc353c 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1047,9 +1047,9 @@ namespace transport auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () : (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ()); - if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) + if (!addr) { - LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: Address not found in SessionConfirmed"); Terminate (); return; } @@ -1069,6 +1069,14 @@ namespace transport SendTerminationAndTerminate (eNTCP2Banned); return; } + if (memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) + { + LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); + if (addr->IsPublishedNTCP2 ()) + i2p::transport::transports.AddBan (m_RemoteEndpoint.address ()); + Terminate (); + return; + } // TODO: process options block // ready to communicate @@ -1936,7 +1944,7 @@ namespace transport if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (!i2p::transport::transports.IsInReservedRange(ep.address ())) + if (!i2p::transport::transports.IsInReservedRange(ep.address ()) && !i2p::transport::transports.IsBanned(ep.address ())) { if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) { @@ -1948,7 +1956,7 @@ namespace transport LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); + LogPrint (eLogError, "NTCP2: Incoming connection from invalid or banned IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); @@ -1983,8 +1991,9 @@ namespace transport if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (!i2p::transport::transports.IsInReservedRange(ep.address ()) || - i2p::util::net::IsYggdrasilAddress (ep.address ())) + if ((!i2p::transport::transports.IsInReservedRange(ep.address ()) || + i2p::util::net::IsYggdrasilAddress (ep.address ())) && + !i2p::transport::transports.IsBanned(ep.address ())) { if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) { @@ -1996,7 +2005,7 @@ namespace transport LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); + LogPrint (eLogError, "NTCP2: Incoming connection from invalid or banned IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index 93f81139..ab85f9f8 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -225,40 +225,40 @@ namespace transport std::lock_guard l(m_ConnectedRecentlyMutex); auto it = m_ConnectedRecently.find (ep); if (it != m_ConnectedRecently.end ()) - { + { if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION)) return true; else if (max) m_ConnectedRecently.erase (it); - } + } return false; - } + } void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts) { - if (!ep.port () || ep.address ().is_unspecified () || + if (!ep.port () || ep.address ().is_unspecified () || 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 - } - + } + void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr from) { if (offset) - { + { if (m_PendingTimeOffset) // one more - { - if (m_PendingTimeOffsetFrom && from && + { + if (m_PendingTimeOffsetFrom && from && m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers - { + { if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW) - { + { offset = (m_PendingTimeOffset + offset)/2; // average LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds"); i2p::util::AdjustTimeOffset (offset); - } + } else LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted"); m_PendingTimeOffset = 0; @@ -268,18 +268,18 @@ namespace transport LogPrint (eLogWarning, "SSU2: Time offsets from same router. Clock not adjusted"); } else - { - m_PendingTimeOffset = offset; // first + { + m_PendingTimeOffset = offset; // first m_PendingTimeOffsetFrom = from; - } - } + } + } else - { + { m_PendingTimeOffset = 0; // reset m_PendingTimeOffsetFrom = nullptr; - } - } - + } + } + boost::asio::ip::udp::socket& SSU2Server::OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint) { boost::asio::ip::udp::socket& socket = localEndpoint.address ().is_v6 () ? m_SocketV6 : m_SocketV4; @@ -289,14 +289,14 @@ namespace transport socket.close (); socket.open (localEndpoint.protocol ()); if (localEndpoint.address ().is_v6 ()) -#if !defined(__HAIKU__) +#if !defined(__HAIKU__) socket.set_option (boost::asio::ip::v6_only (true)); #else { LogPrint (eLogWarning, "SSU2: Socket option IPV6_V6ONLY is not supported"); m_IsForcedFirewalled6 = true; // IPV6_V6ONLY is not supported, always Firewalled for ipv6 - } -#endif + } +#endif uint64_t bufferSize = i2p::context.GetBandwidthLimit() * 1024 / 5; // max lag = 200ms bufferSize = std::max(SSU2_SOCKET_MIN_BUFFER_SIZE, std::min(bufferSize, SSU2_SOCKET_MAX_BUFFER_SIZE)); @@ -377,9 +377,9 @@ namespace transport m_PacketsPool.ReleaseMt (packet); Receive (socket); return; - } + } packet->len = bytes_transferred; - + boost::system::error_code ec; size_t moreBytes = socket.available (ec); if (!ec && moreBytes) @@ -387,7 +387,7 @@ namespace transport std::list packets; packets.push_back (packet); while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH) - { + { packet = m_PacketsPool.AcquireMt (); packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); if (!ec) @@ -410,7 +410,7 @@ namespace transport InsertToReceivedPacketsQueue (packets); } else - InsertToReceivedPacketsQueue (packet); + InsertToReceivedPacketsQueue (packet); Receive (socket); } else @@ -462,7 +462,7 @@ namespace transport } if (empty) boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); - } + } void SSU2Server::InsertToReceivedPacketsQueue (std::list& packets) { @@ -478,12 +478,12 @@ namespace transport 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 (!queueSize) boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); }); - } - + } + void SSU2Server::HandleReceivedPacketsQueue () { std::list receivedPackets; @@ -492,18 +492,18 @@ namespace transport m_ReceivedPacketsQueue.swap (receivedPackets); } HandleReceivedPackets (std::move (receivedPackets)); - } - + } + bool SSU2Server::AddSession (std::shared_ptr session) { if (session) { if (m_Sessions.emplace (session->GetConnID (), session).second) - { + { if (session->GetState () != eSSU2SessionStatePeerTest) AddSessionByRouterHash (session); return true; - } + } } return false; } @@ -514,7 +514,7 @@ namespace transport if (it != m_Sessions.end ()) { if (it->second->GetState () != eSSU2SessionStatePeerTest) - { + { auto ident = it->second->GetRemoteIdentity (); if (ident) { @@ -522,7 +522,7 @@ namespace transport auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ()); if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second.lock ()) m_SessionsByRouterHash.erase (it1); - } + } } if (m_LastSession == it->second) m_LastSession = nullptr; @@ -533,8 +533,8 @@ namespace transport void SSU2Server::RequestRemoveSession (uint64_t connID) { boost::asio::post (GetService (), [connID, this]() { RemoveSession (connID); }); - } - + } + void SSU2Server::AddSessionByRouterHash (std::shared_ptr session) { if (session) @@ -551,17 +551,17 @@ namespace transport oldSession = ret.first->second.lock (); // update session ret.first->second = session; - } - } + } + } if (oldSession && oldSession != session) - { + { // session already exists LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); // move unsent msgs to new session oldSession->MoveSendQueue (session); // terminate existing boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession)); - } + } } } } @@ -578,7 +578,7 @@ namespace transport std::lock_guard l(m_SessionsByRouterHashMutex); auto it = m_SessionsByRouterHash.find (ident); if (it != m_SessionsByRouterHash.end ()) - { + { if (!it->second.expired ()) { auto s = it->second.lock (); @@ -586,7 +586,7 @@ namespace transport return s; } m_SessionsByRouterHash.erase (it); - } + } return nullptr; } @@ -647,11 +647,11 @@ namespace transport if (it != m_Relays.end ()) { if (!it->second.expired ()) - { + { auto s = it->second.lock (); if (s && s->IsEstablished ()) return s; - } + } m_Relays.erase (it); } return nullptr; @@ -660,7 +660,7 @@ namespace transport bool SSU2Server::AddPeerTest (uint32_t nonce, std::shared_ptr aliceSession, uint64_t ts) { return m_PeerTests.emplace (nonce, std::pair{ aliceSession, ts }).second; - } + } std::shared_ptr SSU2Server::GetPeerTest (uint32_t nonce) { @@ -672,13 +672,13 @@ namespace transport return s; } return nullptr; - } - + } + bool SSU2Server::AddRequestedPeerTest (uint32_t nonce, std::shared_ptr session, uint64_t ts) { return m_RequestedPeerTests.emplace (nonce, std::pair{ session, ts }).second; } - + std::shared_ptr SSU2Server::GetRequestedPeerTest (uint32_t nonce) { auto it = m_RequestedPeerTests.find (nonce); @@ -689,8 +689,8 @@ namespace transport return s; } return nullptr; - } - + } + void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { if (len < 24) return; @@ -740,7 +740,7 @@ namespace transport break; case eSSU2SessionStateHolePunch: m_LastSession->ProcessFirstIncomingMessage (connID, buf, len); // SessionRequest - break; + break; case eSSU2SessionStateClosing: m_LastSession->ProcessData (buf, len, senderEndpoint); // we might receive termintaion block if (m_LastSession && m_LastSession->GetState () == eSSU2SessionStateClosing) @@ -769,7 +769,8 @@ namespace transport else it1->second->ProcessRetry (buf, len); } - else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) + else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port () && + !i2p::transport::transports.IsBanned (senderEndpoint.address ())) { // assume new incoming session auto session = std::make_shared (*this); @@ -777,7 +778,7 @@ namespace transport session->ProcessFirstIncomingMessage (connID, buf, len); } else - LogPrint (eLogError, "SSU2: Incoming packet received from invalid endpoint ", senderEndpoint); + LogPrint (eLogError, "SSU2: Incoming packet received from invalid or banned endpoint ", senderEndpoint); } } @@ -875,8 +876,8 @@ namespace transport return true; } return false; - } - + } + bool SSU2Server::CreateSession (std::shared_ptr router, std::shared_ptr address, bool peerTest) { @@ -909,8 +910,8 @@ namespace transport if (CheckPendingOutgoingSession (ep, peerTest)) return false; session->SetRemoteEndpoint (ep); isValidEndpoint = true; - } - } + } + } if (peerTest) session->SetOnEstablished ([session]() {session->SendPeerTest (); }); @@ -944,14 +945,14 @@ namespace transport { auto addr = s->GetAddress (); if (addr && addr->IsIntroducer ()) - { + { s->Introduce (session, it.iTag); return; } } else indices.push_back(i); - } + } i++; } // we have to start a new session to an introducer @@ -970,9 +971,9 @@ namespace transport // introducer is not expired, because in indices r = i2p::data::netdb.FindRouter (introducer.iH); if (r) - { + { if (r->IsPublishedOn (i2p::context.GetRouterInfo ().GetCompatibleTransports (false) & // outgoing - (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))) + (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6))) { relayTag = introducer.iTag; addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); @@ -986,27 +987,27 @@ namespace transport { if (i2p::context.SupportsV6 ()) addr = r->GetSSU2V6Address (); - } + } else { if (i2p::context.SupportsV4 ()) addr = r->GetSSU2V4Address (); - } + } if (addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port && !i2p::transport::transports.IsInReservedRange(addr->host)) break; else - { + { // all addresses are invalid, try next introducer relayTag = 0; addr = nullptr; r = nullptr; - } - } + } + } } else r = nullptr; - } + } else if (!i2p::data::IsRouterBanned (introducer.iH)) newRouters.push_back (introducer.iH); } @@ -1058,7 +1059,7 @@ namespace transport { auto remoteAddr = session->GetAddress (); if (!remoteAddr || !remoteAddr->IsPeerTesting () || - (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; + (v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false; if (session->IsEstablished ()) boost::asio::post (GetService (), [session]() { session->SendPeerTest (); }); else @@ -1096,7 +1097,7 @@ namespace transport else it++; } - } + } for (auto it: m_Sessions) { @@ -1148,7 +1149,7 @@ namespace transport else it++; } - + for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); ) { if (ts > it->second.second) @@ -1171,7 +1172,7 @@ namespace transport it = m_ConnectedRecently.erase (it); else it++; - } + } for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();) { @@ -1190,8 +1191,8 @@ namespace transport else it++; } - } - + } + m_PacketsPool.CleanUpMt (); m_SentPacketsPool.CleanUp (); m_IncompleteMessagesPool.CleanUp (); @@ -1202,7 +1203,7 @@ namespace transport void SSU2Server::ScheduleResend (bool more) { - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? (SSU2_RESEND_CHECK_MORE_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE): (SSU2_RESEND_CHECK_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE))); m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer, @@ -1283,7 +1284,7 @@ namespace transport { std::vector > ret; if (maxNumIntroducers <= 0 || m_Sessions.empty ()) return ret; - + std::vector > eligible; eligible.reserve (m_Sessions.size ()/2); auto ts = i2p::util::GetSecondsSinceEpoch (); @@ -1299,7 +1300,7 @@ namespace transport if (eligible.size () <= (size_t)maxNumIntroducers) return eligible; - else + else std::sample (eligible.begin(), eligible.end(), std::back_inserter(ret), maxNumIntroducers, m_Rng); return ret; } @@ -1316,31 +1317,31 @@ namespace transport if (session) excluded.insert (ident); if (session) - { + { if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer? ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) - { + { session->SendKeepAlive (); - if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) - { + if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) + { newList.push_back ({ident, session->GetRelayTag ()}); if (tag != session->GetRelayTag ()) { LogPrint (eLogDebug, "SSU2: Introducer session to ", session->GetIdentHashBase64() , " was replaced. iTag ", tag, "->", session->GetRelayTag ()); i2p::context.UpdateSSU2Introducer (ident, v4, session->GetRelayTag (), session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION); - } + } } - else - { + else + { impliedList.push_back ({ident, session->GetRelayTag ()}); // keep in introducers list, but not publish - session = nullptr; + session = nullptr; } - } + } else session = nullptr; - } - + } + if (!session) i2p::context.RemoveSSU2Introducer (ident, v4); } @@ -1356,12 +1357,12 @@ namespace transport auto session = FindSession (it.first); if (session) { - if (std::find_if (newList.begin (), newList.end (), + if (std::find_if (newList.begin (), newList.end (), [&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ()) - { + { sessions.push_back (session); numOldSessions++; - } + } } } impliedList.clear (); @@ -1369,9 +1370,9 @@ namespace transport for (const auto& it : sessions) { - uint32_t tag = it->GetRelayTag (); + uint32_t tag = it->GetRelayTag (); uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; - if (!tag && ts >= exp) + if (!tag && ts >= exp) continue; // don't publish expired introducer i2p::data::RouterInfo::Introducer introducer; introducer.iTag = tag; @@ -1393,14 +1394,14 @@ namespace transport if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS || numOldSessions) { // we need to create more sessions with relay tag - + // exclude all existing sessions excluded.clear (); { std::lock_guard l(m_SessionsByRouterHashMutex); for (const auto& [ident, s] : m_SessionsByRouterHash) excluded.insert (ident); - } + } // session about to expire are not counted for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS + numOldSessions; i++) @@ -1544,8 +1545,8 @@ namespace transport void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) { m_ChaCha20 (msg, msgLen, key, nonce, out); - } - + } + void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen, const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to) { diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index ecd564d7..19d05dce 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -1200,9 +1200,9 @@ namespace transport ri = ri1; m_Address = m_RemoteEndpoint.address ().is_v6 () ? ri->GetSSU2V6Address () : ri->GetSSU2V4Address (); - if (!m_Address || memcmp (S, m_Address->s, 32)) + if (!m_Address) { - LogPrint (eLogError, "SSU2: Wrong static key in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + LogPrint (eLogError, "SSU2: Address not found in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); return false; } if (m_Address->published && m_RemoteEndpoint.address () != m_Address->host && @@ -1220,6 +1220,13 @@ namespace transport " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); return false; } + if (memcmp (S, m_Address->s, 32)) + { + LogPrint (eLogError, "SSU2: Wrong static key in SessionConfirmed from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + if (m_Address->published) + i2p::transport::transports.AddBan (m_RemoteEndpoint.address ()); + return false; + } if (!m_Address->published) { if (ri->HasProfile ()) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index ebe715ac..94e74ba6 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -187,6 +187,7 @@ namespace transport m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service); + m_BanListCleanupTimer = std::make_unique(*m_Service); } bool ipv4; i2p::config::GetOption("ipv4", ipv4); @@ -347,12 +348,15 @@ namespace transport m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } + m_BanListCleanupTimer->expires_after (std::chrono::seconds(BAN_LIST_CLEANUP_INTERVAL + m_Rng () % BAN_LIST_CLEANUP_INTERVAL_VARIANCE)); + m_BanListCleanupTimer->async_wait (std::bind (&Transports::HandleBanListCleanupTimer, this, std::placeholders::_1)); } void Transports::Stop () { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); + if (m_BanListCleanupTimer) m_BanListCleanupTimer->cancel (); if (m_SSU2Server) { @@ -1076,6 +1080,29 @@ namespace transport } } + void Transports::HandleBanListCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (!m_BanList.empty ()) + { + auto ts = i2p::util::GetMonotonicSeconds (); + { + std::lock_guard l(m_BanListMutex); + for (auto it = m_BanList.begin (); it != m_BanList.end (); ) + { + if (ts < it->second) + it++; + else + it = m_BanList.erase (it); + } + } + } + m_BanListCleanupTimer->expires_after (std::chrono::seconds(BAN_LIST_CLEANUP_INTERVAL + m_Rng () % BAN_LIST_CLEANUP_INTERVAL_VARIANCE)); + m_BanListCleanupTimer->async_wait (std::bind (&Transports::HandleBanListCleanupTimer, this, std::placeholders::_1)); + } + } + template std::shared_ptr Transports::GetRandomPeer (Filter filter) const { @@ -1299,6 +1326,27 @@ namespace transport return IsCheckReserved () && i2p::util::net::IsInReservedRange (host); } + bool Transports::IsBanned (const boost::asio::ip::address& addr) + { + std::lock_guard l(m_BanListMutex); + auto it = m_BanList.find (addr); + if (it != m_BanList.end ()) + { + if (it->second < i2p::util::GetMonotonicSeconds ()) + return true; + else + m_BanList.erase (it); + } + return false; + } + + bool Transports::AddBan (const boost::asio::ip::address& addr) + { + auto ts = i2p::util::GetMonotonicSeconds () + IP_BAN_TIME + m_Rng () % IP_BAN_TIME_VARIANCE; + std::lock_guard l(m_BanListMutex); + return m_BanList.emplace (addr, ts).second; + } + void InitAddressFromIface () { bool ipv6; i2p::config::GetOption("ipv6", ipv6); diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index bd002c78..6288d2ef 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -116,8 +116,12 @@ namespace transport 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 + const int IP_BAN_TIME = 1800; // in seconds + const int IP_BAN_TIME_VARIANCE = 540; // in seconds + const int BAN_LIST_CLEANUP_INTERVAL = 66; // in seconds + const int BAN_LIST_CLEANUP_INTERVAL_VARIANCE = 20; // in seconds + const int TRAFFIC_SAMPLE_COUNT = 301; // seconds struct TrafficSample { uint64_t Timestamp; @@ -190,6 +194,9 @@ namespace transport bool IsCheckReserved () const { return m_CheckReserved; }; bool IsInReservedRange (const boost::asio::ip::address& host) const; + bool IsBanned (const boost::asio::ip::address& addr); + bool AddBan (const boost::asio::ip::address& addr); + private: void Run (); @@ -201,6 +208,7 @@ namespace transport void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); + void HandleBanListCleanupTimer (const boost::system::error_code& ecode); void UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit); void DetectExternalIP (); @@ -247,6 +255,11 @@ namespace transport i2p::I2NPMessagesHandler m_LoopbackHandler; std::mt19937 m_Rng; + std::map m_BanList; // ip->expiration time in seconds + mutable std::mutex m_BanListMutex; + std::unique_ptr m_BanListCleanupTimer; + + public: // for HTTP only