From 975dab6d1daed97ad4bab66daf481bc2815cc58b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 14 Nov 2016 08:38:25 -0500 Subject: [PATCH 01/12] add hacking.md for notes on internal structure --- docs/hacking.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/hacking.md diff --git a/docs/hacking.md b/docs/hacking.md new file mode 100644 index 00000000..163575f0 --- /dev/null +++ b/docs/hacking.md @@ -0,0 +1,114 @@ + +# Hacking on I2PD + +This document contains notes compiled from hacking on i2pd + +## prerequisites + +This guide assumes: + +* a decent understanding of c++ +* basic understanding of how i2p works at i2np level and up + +## general structure + +Notes on multithreading + +* every compontent runs in its own thread + +* each component (usually) has a public function `GetService()` which can be used to obtain the `boost::asio::io_service` that it uses. + +* when talking between components/threads, **always** use `GetService().post()` and be mindfull of stack allocated memory. + + +### NetDb + +#### NetDb.h + +The `i2p::data::netdb` is a `i2p::data::NetDb` instance processes and dispatches *inbound* i2np messages passed in from transports. + +global singleton at `i2p::data::netdb` as of 2.10.1 + +#### NetDbRequests.h + +For Pending RouterInfo/LeaseSet lookup and store requests + + +### ClientContext + +#### ClientContext.h + +`i2p::client::ClientContext` spawns all destinations used by the i2p router including the shared local destination. + +global singleton at `i2p::client::context` as of 2.10.1 + + + +### Daemon + +File: Daemon.cpp + +`i2p::util::Daemon_Singleton_Private` subclasses implement the daemon start-up and tear-down, creates Http Webui and i2p control server. + + + + +### Destinations + +#### Destination.h + +each destination runs in its own thread + +##### i2p::client::LeaseSetDestination + +Base for `i2p::client::ClientDestination` + +##### i2p::client::ClientDestination + +Destination capable of creating (tcp/i2p) streams and datagram sessions. + + +#### Streaming.h + +##### i2p::stream::StreamingDestination + +Does not implement any destination related members, the name is a bit misleading. + +Owns a `i2p::client::ClientDestination` and runs in the destination thread. + +Anyone creating or using streams outside of the destination thread **MUST** be aware of the consequences of multithreaded c++ :^) + +If you use streaming please consider running all code within the destination thread using `ClientDestination::GetService().post()` + + +#### Garlic.h + +Provides Inter-Destination routing primatives. + +##### i2p::garlic::GarlicDestination + +sublcass of `i2p::client::LeaseSetDestination` for sending messages down shared routing paths. + +##### i2p::garlic::GarlicRoutingSession + +a point to point conversation between us and 1 other destination. + +##### i2p::garlic::GarlicRoutingPath + +A routing path currently used by a routing session. specifies which outbound tunnel to use and which remote lease set to use for `OBEP` to `IBGW` inter tunnel communication. + +members: + +* outboundTunnel (OBEP) +* remoteLease (IBGW) +* rtt (round trip time) +* updatedTime (last time this path's IBGW/OBEP was updated) +* numTimesUsesd (number of times this path was used) + +### Transports + +each transport runs in its own thread + +#### Transports.h + +`i2p::transport::Transports` contains NTCP and SSU transport instances From 6b5b9b3d62b556abdc8450bf44e3b10ac79a7422 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 14 Nov 2016 12:05:44 -0500 Subject: [PATCH 02/12] add reseed from floodfill option --- Config.cpp | 3 +- NetDb.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++--- NetDb.h | 9 ++++++ NetDbRequests.cpp | 9 ++++-- Tag.h | 8 ++++- 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/Config.cpp b/Config.cpp index aa75b8db..71fcbfbd 100644 --- a/Config.cpp +++ b/Config.cpp @@ -157,7 +157,8 @@ namespace config { options_description reseed("Reseed options"); reseed.add_options() - ("reseed.verify", value()->default_value(false), "Verify .su3 signature") + ("reseed.verify", value()->default_value(false), "Verify .su3 signature") + ("reseed.floodfill", value()->default_value(""), "Path to router info of floodfill to reseed from") ("reseed.file", value()->default_value(""), "Path to .su3 file") ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," diff --git a/NetDb.cpp b/NetDb.cpp index dbfa4bcb..e667dc11 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -14,6 +14,7 @@ #include "RouterContext.h" #include "Garlic.h" #include "NetDb.h" +#include "Config.h" using namespace i2p::transport; @@ -23,7 +24,7 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_FloodfillBootstrap(nullptr), m_HiddenMode(false) { } @@ -140,6 +141,8 @@ namespace data LogPrint(eLogError, "NetDb: no known routers, reseed seems to be totally failed"); break; } + else // we have peers now + m_FloodfillBootstrap = nullptr; if (numRouters < 2500 || ts - lastExploratory >= 90) { numRouters = 800/numRouters; @@ -295,13 +298,62 @@ namespace data m_Reseeder = new Reseeder (); m_Reseeder->LoadCertificates (); // we need certificates for SU3 verification } - int reseedRetries = 0; + int reseedRetries = 0; + + // try reseeding from floodfill first if specified + std::string riPath; + if(i2p::config::GetOption("reseed.floodfill", riPath)) { + auto ri = std::make_shared(riPath); + if (ri->IsFloodfill()) { + const uint8_t * riData = ri->GetBuffer(); + int riLen = ri->GetBufferLen(); + if(!i2p::data::netdb.AddRouterInfo(riData, riLen)) { + // bad router info + LogPrint(eLogError, "NetDb: bad router info"); + return; + } + m_FloodfillBootstrap = ri; + ReseedFromFloodfill(*ri); + // don't try reseed servers if trying to boostrap from floodfill + return; + } + } + while (reseedRetries < 10 && !m_Reseeder->ReseedNowSU3 ()) reseedRetries++; if (reseedRetries >= 10) LogPrint (eLogWarning, "NetDb: failed to reseed after 10 attempts"); } + void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills) + { + LogPrint(eLogInfo, "NetDB: reseeding from floodfill ", ri.GetIdentHashBase64()); + std::vector > requests; + + i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); + i2p::data::IdentHash ih = ri.GetIdentHash(); + i2p::data::IdentHash randomIdent; + + // make floodfill lookups + while(numFloodfills > 0) { + randomIdent.Randomize(); + auto msg = i2p::CreateRouterInfoDatabaseLookupMsg(randomIdent, ourIdent, 0, false); + requests.push_back(msg); + numFloodfills --; + } + + // make regular router lookups + while(numRouters > 0) { + randomIdent.Randomize(); + auto msg = i2p::CreateRouterInfoDatabaseLookupMsg(randomIdent, ourIdent, 0, true); + requests.push_back(msg); + numRouters --; + } + + // send them off + i2p::transport::transports.SendMessages(ih, requests); + } + bool NetDb::LoadRouterInfo (const std::string & path) { auto r = std::make_shared(path); @@ -498,6 +550,21 @@ namespace data m_Requests.RequestComplete (destination, nullptr); } } + + void NetDb::RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete) + { + + auto dest = m_Requests.CreateRequest (destination, exploritory, requestComplete); // non-exploratory + if (!dest) + { + LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase64(), " is requested already"); + return; + } + LogPrint(eLogInfo, "NetDb: destination ", destination.ToBase64(), " being requested directly from ", from.ToBase64()); + // direct + transports.SendMessage (from, dest->CreateRequestMessage (nullptr, nullptr)); + } + void NetDb::HandleDatabaseStoreMsg (std::shared_ptr m) { @@ -620,7 +687,7 @@ namespace data if (!dest->IsExploratory ()) { // reply to our destination. Try other floodfills - if (outbound && inbound ) + if (outbound && inbound) { std::vector msgs; auto count = dest->GetExcludedPeers ().size (); @@ -664,7 +731,7 @@ namespace data // no more requests for detination possible. delete it m_Requests.RequestComplete (ident, nullptr); } - else + else if(!m_FloodfillBootstrap) LogPrint (eLogWarning, "NetDb: requested destination for ", key, " not found"); // try responses @@ -681,7 +748,10 @@ namespace data { // router with ident not found or too old (1 hour) LogPrint (eLogDebug, "NetDb: found new/outdated router. Requesting RouterInfo ..."); - RequestDestination (router); + if(m_FloodfillBootstrap) + RequestDestinationFrom(router, m_FloodfillBootstrap->GetIdentHash(), true); + else + RequestDestination (router); } else LogPrint (eLogDebug, "NetDb: [:|||:]"); diff --git a/NetDb.h b/NetDb.h index d295ebbe..94c8a086 100644 --- a/NetDb.h +++ b/NetDb.h @@ -60,6 +60,7 @@ namespace data std::shared_ptr FindRouterProfile (const IdentHash& ident) const; void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); + void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr); void HandleDatabaseStoreMsg (std::shared_ptr msg); void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); @@ -98,6 +99,9 @@ namespace data void VisitRouterInfos(RouterInfoVisitor v); /** visit N random router that match using filter, then visit them with a visitor, return number of RouterInfos that were visited */ size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n); + + + private: void Load (); @@ -110,6 +114,8 @@ namespace data void ManageRequests (); void ManageLookupResponses (); + void ReseedFromFloodfill(const RouterInfo & ri, int numRouters=40, int numFloodfills=20); + template std::shared_ptr GetRandomRouter (Filter filter) const; @@ -135,6 +141,9 @@ namespace data friend class NetDbRequests; NetDbRequests m_Requests; + /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ + std::shared_ptr m_FloodfillBootstrap; + std::map, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp) /** true if in hidden mode */ diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index 474d3693..866bc6d9 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -11,10 +11,15 @@ namespace data std::shared_ptr RequestedDestination::CreateRequestMessage (std::shared_ptr router, std::shared_ptr replyTunnel) { - auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, + std::shared_ptr msg; + if(replyTunnel) + msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, &m_ExcludedPeers); - m_ExcludedPeers.insert (router->GetIdentHash ()); + else + msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); + if(router) + m_ExcludedPeers.insert (router->GetIdentHash ()); m_CreationTime = i2p::util::GetSecondsSinceEpoch (); return msg; } diff --git a/Tag.h b/Tag.h index 30dfa654..3d21183f 100644 --- a/Tag.h +++ b/Tag.h @@ -11,6 +11,7 @@ #include #include +#include #include "Base.h" namespace i2p { @@ -49,7 +50,12 @@ public: { memset(m_Buf, c, sz); } - + + void Randomize() + { + RAND_bytes(m_Buf, sz); + } + std::string ToBase64 () const { char str[sz*2]; From fc94e846a6a30eccdc811c83c81ad21eef7f8c27 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 10:20:09 -0500 Subject: [PATCH 03/12] add latency requirement option --- ClientContext.cpp | 8 +++++++- Config.cpp | 8 ++++++-- Destination.cpp | 16 ++++++++++++++++ Destination.h | 6 ++++++ Tunnel.cpp | 23 +++++++++++++++++++++++ Tunnel.h | 25 +++++++++++++++++++++++++ TunnelPool.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++-- TunnelPool.h | 15 +++++++++++++++ 8 files changed, 141 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4639ec05..52423a23 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -372,6 +372,8 @@ namespace client options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); + options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); + options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const @@ -384,7 +386,11 @@ namespace client if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, value)) - options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) + options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) + options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; } void ClientContext::ReadTunnels () diff --git a/Config.cpp b/Config.cpp index 8d932048..ed22c561 100644 --- a/Config.cpp +++ b/Config.cpp @@ -86,7 +86,9 @@ namespace config { ("httpproxy.inbound.length", value()->default_value("3"), "HTTP proxy inbound tunnel length") ("httpproxy.outbound.length", value()->default_value("3"), "HTTP proxy outbound tunnel length") ("httpproxy.inbound.quantity", value()->default_value("5"), "HTTP proxy inbound tunnels quantity") - ("httpproxy.outbound.quantity", value()->default_value("5"), "HTTP proxy outbound tunnels quantity") + ("httpproxy.outbound.quantity", value()->default_value("5"), "HTTP proxy outbound tunnels quantity") + ("httpproxy.latency.min", value()->default_value(0), "HTTP proxy min latency for tunnels") + ("httpproxy.latency.max", value()->default_value(0), "HTTP proxy max latency for tunnels") ; options_description socksproxy("SOCKS Proxy options"); @@ -98,7 +100,9 @@ namespace config { ("socksproxy.inbound.length", value()->default_value("3"), "SOCKS proxy inbound tunnel length") ("socksproxy.outbound.length", value()->default_value("3"), "SOCKS proxy outbound tunnel length") ("socksproxy.inbound.quantity", value()->default_value("5"), "SOCKS proxy inbound tunnels quantity") - ("socksproxy.outbound.quantity", value()->default_value("5"), "SOCKS proxy outbound tunnels quantity") + ("socksproxy.outbound.quantity", value()->default_value("5"), "SOCKS proxy outbound tunnels quantity") + ("socksproxy.latency.min", value()->default_value(0), "SOCKS proxy min latency for tunnels") + ("socksproxy.latency.max", value()->default_value(0), "SOCKS proxy max latency for tunnels") ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") ; diff --git a/Destination.cpp b/Destination.cpp index 9df3b438..f0bc52cb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -63,6 +63,22 @@ namespace client m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); + if(params) + { + auto itr = params->find(I2CP_PARAM_MAX_TUNNEL_LATENCY); + if (itr != params->end()) { + auto maxlatency = std::stoi(itr->second); + itr = params->find(I2CP_PARAM_MIN_TUNNEL_LATENCY); + if (itr != params->end()) { + auto minlatency = std::stoi(itr->second); + if ( minlatency > 0 && maxlatency > 0 ) { + // set tunnel pool latency + LogPrint(eLogInfo, "Destination: requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]"); + m_Pool->RequireLatency(minlatency, maxlatency); + } + } + } + } } LeaseSetDestination::~LeaseSetDestination () diff --git a/Destination.h b/Destination.h index 121b7e16..0d3b3b4a 100644 --- a/Destination.h +++ b/Destination.h @@ -50,6 +50,12 @@ namespace client const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; const int DEFAULT_TAGS_TO_SEND = 40; + // latency + const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min"; + const int DEFAULT_MIN_TUNNEL_LATENCY = 0; + const char I2CP_PARAM_MAX_TUNNEL_LATENCY[] = "latency.max"; + const int DEFAULT_MAX_TUNNEL_LATENCY = 0; + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, diff --git a/Tunnel.cpp b/Tunnel.cpp index faba99d3..2d55547c 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -21,6 +21,23 @@ namespace i2p namespace tunnel { + void TunnelLatency::AddSample(Sample s) + { + m_samples ++; + m_latency += s / m_samples; + } + + bool TunnelLatency::HasSamples() const + { + return m_samples > 0; + } + + TunnelLatency::Latency TunnelLatency::GetMeanLatency() const + { + return m_latency; + } + + Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false) @@ -162,6 +179,12 @@ namespace tunnel return established; } + bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const + { + auto latency = GetMeanLatency(); + return latency >= lower && latency <= upper; + } + void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { const uint8_t * inPayload = in->GetPayload () + 4; diff --git a/Tunnel.h b/Tunnel.h index 53c93e90..8712f56d 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -79,6 +79,22 @@ namespace tunnel eTunnelStateExpiring }; + /** @brief for storing latency history */ + struct TunnelLatency + { + typedef uint64_t Sample; + typedef uint64_t Latency; + + + void AddSample(Sample s); + bool HasSamples() const; + Latency GetMeanLatency() const; + + Latency m_latency = 0; + std::size_t m_samples = 0; + + }; + class OutboundTunnel; class InboundTunnel; class Tunnel: public TunnelBase @@ -118,6 +134,14 @@ namespace tunnel void SendTunnelDataMsg (std::shared_ptr msg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); + /** @brief add latency sample */ + void AddLatencySample(const uint64_t ms) { m_Latency.AddSample(ms); } + /** @brief get this tunnel's estimated latency */ + uint64_t GetMeanLatency() const { return m_Latency.GetMeanLatency(); } + /** @breif return true if this tunnel's latency fits in range [lowerbound, upperbound] */ + bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; + + bool LatencyIsKnown() const { return m_Latency.HasSamples(); } protected: void PrintHops (std::stringstream& s) const; @@ -129,6 +153,7 @@ namespace tunnel std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; bool m_IsRecreated; + TunnelLatency m_Latency; }; class OutboundTunnel: public Tunnel diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 56d72bfb..625c7d11 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -147,12 +147,16 @@ namespace tunnel std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { - std::unique_lock l(m_OutboundTunnelsMutex); + if (HasLatencyRequriement()) + return GetLowestLatencyOutboundTunnel(excluded); + std::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, excluded); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { + if (HasLatencyRequriement()) + return GetLowestLatencyInboundTunnel(excluded); std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded); } @@ -322,7 +326,12 @@ namespace tunnel test.first->SetState (eTunnelStateEstablished); if (test.second->GetState () == eTunnelStateTestFailed) test.second->SetState (eTunnelStateEstablished); - LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); + uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; + LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); + // update latency + uint64_t latency = dlt / 2; + test.first->AddLatencySample(latency); + test.second->AddLatencySample(latency); } else { @@ -523,5 +532,37 @@ namespace tunnel std::lock_guard lock(m_CustomPeerSelectorMutex); return m_CustomPeerSelector != nullptr; } + + std::shared_ptr TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr exclude) const + { + std::shared_ptr tun = nullptr; + std::unique_lock lock(m_InboundTunnelsMutex); + uint64_t min = 1000000; + for (const auto & itr : m_InboundTunnels) { + if(!itr->LatencyIsKnown()) continue; + auto l = itr->GetMeanLatency(); + if (l >= min) continue; + tun = itr; + if(tun == exclude) continue; + min = l; + } + return tun; + } + + std::shared_ptr TunnelPool::GetLowestLatencyOutboundTunnel(std::shared_ptr exclude) const + { + std::shared_ptr tun = nullptr; + std::unique_lock lock(m_OutboundTunnelsMutex); + uint64_t min = 1000000; + for (const auto & itr : m_OutboundTunnels) { + if(!itr->LatencyIsKnown()) continue; + auto l = itr->GetMeanLatency(); + if (l >= min) continue; + tun = itr; + if(tun == exclude) continue; + min = l; + } + return tun; + } } } diff --git a/TunnelPool.h b/TunnelPool.h index d5bcf18f..ce20be6b 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -69,6 +69,17 @@ namespace tunnel void SetCustomPeerSelector(TunnelPeerSelector selector); void UnsetCustomPeerSelector(); bool HasCustomPeerSelector(); + + /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ + void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } + + /** @brief return true if this tunnel pool has a latency requirement */ + bool HasLatencyRequriement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } + + /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ + std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; + std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude=nullptr) const; + private: void CreateInboundTunnel (); @@ -94,6 +105,10 @@ namespace tunnel bool m_IsActive; std::mutex m_CustomPeerSelectorMutex; TunnelPeerSelector m_CustomPeerSelector; + + uint64_t m_MinLatency=0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms + uint64_t m_MaxLatency=0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms + public: // for HTTP only From 7fef5f5654df6065c3129c23f6a2111d3cc76b84 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 10:37:58 -0500 Subject: [PATCH 04/12] when selecting tunnels if we can't find a low latency tunnel fall back to regular selection algorithm --- TunnelPool.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 625c7d11..2680e8b9 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -147,18 +147,26 @@ namespace tunnel std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { + std::shared_ptr tun = nullptr; if (HasLatencyRequriement()) - return GetLowestLatencyOutboundTunnel(excluded); - std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, excluded); + tun = GetLowestLatencyOutboundTunnel(excluded); + if (! tun) { + std::unique_lock l(m_OutboundTunnelsMutex); + tun = GetNextTunnel (m_OutboundTunnels, excluded); + } + return tun; } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { + std::shared_ptr tun = nullptr; if (HasLatencyRequriement()) - return GetLowestLatencyInboundTunnel(excluded); - std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, excluded); + tun = GetLowestLatencyInboundTunnel(excluded); + if (! tun) { + std::unique_lock l(m_InboundTunnelsMutex); + tun = GetNextTunnel (m_InboundTunnels, excluded); + } + return tun; } template From 5425e9aee35fc364adf88dba187b77086cb38a14 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 10:46:58 -0500 Subject: [PATCH 05/12] select tunnels correctly --- TunnelPool.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 2680e8b9..e0a3e99e 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -147,26 +147,14 @@ namespace tunnel std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { - std::shared_ptr tun = nullptr; - if (HasLatencyRequriement()) - tun = GetLowestLatencyOutboundTunnel(excluded); - if (! tun) { - std::unique_lock l(m_OutboundTunnelsMutex); - tun = GetNextTunnel (m_OutboundTunnels, excluded); - } - return tun; + std::unique_lock l(m_OutboundTunnelsMutex); + return GetNextTunnel (m_OutboundTunnels, excluded); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { - std::shared_ptr tun = nullptr; - if (HasLatencyRequriement()) - tun = GetLowestLatencyInboundTunnel(excluded); - if (! tun) { - std::unique_lock l(m_InboundTunnelsMutex); - tun = GetNextTunnel (m_InboundTunnels, excluded); - } - return tun; + std::unique_lock l(m_InboundTunnelsMutex); + return GetNextTunnel (m_InboundTunnels, excluded); } template @@ -179,6 +167,10 @@ namespace tunnel { if (it->IsEstablished () && it != excluded) { + if(HasLatencyRequirement() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { + i ++; + continue; + } tunnel = it; i++; } From 98a55c061394b7ed67efb1caad7bbb0a1f9be221 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 10:48:33 -0500 Subject: [PATCH 06/12] make it compile --- TunnelPool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TunnelPool.h b/TunnelPool.h index ce20be6b..6a73bd67 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -74,7 +74,7 @@ namespace tunnel void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } /** @brief return true if this tunnel pool has a latency requirement */ - bool HasLatencyRequriement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } + bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; From 69888e148e2d2056504387a17fa9889af991e31d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 11:15:48 -0500 Subject: [PATCH 07/12] use correct latency computation --- Tunnel.cpp | 13 +++++++++---- Tunnel.h | 5 ++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 2d55547c..3e9bfc6b 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -23,18 +23,23 @@ namespace tunnel void TunnelLatency::AddSample(Sample s) { - m_samples ++; - m_latency += s / m_samples; + std::unique_lock l(m_access); + m_samples.push_back(s); } bool TunnelLatency::HasSamples() const { - return m_samples > 0; + std::unique_lock l(m_access); + return m_samples.size() > 0; } TunnelLatency::Latency TunnelLatency::GetMeanLatency() const { - return m_latency; + std::unique_lock l(m_access); + Latency l = 0; + for(auto s : m_samples) + l += s; + return l / m_samples.size(); } diff --git a/Tunnel.h b/Tunnel.h index 8712f56d..85590f58 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -90,9 +90,8 @@ namespace tunnel bool HasSamples() const; Latency GetMeanLatency() const; - Latency m_latency = 0; - std::size_t m_samples = 0; - + std::vector m_samples; + std::mutex m_access; }; class OutboundTunnel; From 34afb54c21fee0cd95107896626385e65dde795f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 11:16:32 -0500 Subject: [PATCH 08/12] make it compile --- Tunnel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tunnel.h b/Tunnel.h index 85590f58..f3c31461 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -91,7 +91,7 @@ namespace tunnel Latency GetMeanLatency() const; std::vector m_samples; - std::mutex m_access; + mutable std::mutex m_access; }; class OutboundTunnel; From db63bb4495669ecf0f40bd1688ee36f17e623919 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 11:18:12 -0500 Subject: [PATCH 09/12] make it compile for real --- Tunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 3e9bfc6b..7bfec218 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -35,7 +35,7 @@ namespace tunnel TunnelLatency::Latency TunnelLatency::GetMeanLatency() const { - std::unique_lock l(m_access); + std::unique_lock lock(m_access); Latency l = 0; for(auto s : m_samples) l += s; From 0c5ca28a14766763e8a24eda9c30f5dd12b83f57 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 11:27:00 -0500 Subject: [PATCH 10/12] fall back on regular tunnel algorithm --- TunnelPool.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index e0a3e99e..0c3db211 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -176,6 +176,18 @@ namespace tunnel } if (i > ind && tunnel) break; } + if(HasLatencyRequirement() && !tunnel) { + ind = rand () % (tunnels.size ()/2 + 1), i = 0; + for (const auto& it: tunnels) + { + if (it->IsEstablished () && it != excluded) + { + tunnel = it; + i++; + } + if (i > ind && tunnel) break; + } + } if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded; return tunnel; } From 76c9b66db48764839fd744587cd0cc69099271ae Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 11:31:15 -0500 Subject: [PATCH 11/12] don't blow up --- Tunnel.cpp | 11 +++++++---- TunnelPool.cpp | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 7bfec218..e1f5c035 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -36,10 +36,13 @@ namespace tunnel TunnelLatency::Latency TunnelLatency::GetMeanLatency() const { std::unique_lock lock(m_access); - Latency l = 0; - for(auto s : m_samples) - l += s; - return l / m_samples.size(); + if (m_samples.size() > 0) { + Latency l = 0; + for(auto s : m_samples) + l += s; + return l / m_samples.size(); + } + return 0; } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 0c3db211..ccd4c12c 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -167,7 +167,7 @@ namespace tunnel { if (it->IsEstablished () && it != excluded) { - if(HasLatencyRequirement() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { + if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { i ++; continue; } From 752e74d33cdefd2bcbe9ab8218d29db1321fced3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 15 Nov 2016 14:42:18 -0500 Subject: [PATCH 12/12] show latency of tunnels in web ui --- HTTPServer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 556051aa..bf0a3159 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -300,12 +300,16 @@ namespace http { s << "Inbound tunnels:
\r\n"; for (auto & it : pool->GetInboundTunnels ()) { it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } s << "
\r\n"; s << "Outbound tunnels:
\r\n"; for (auto & it : pool->GetOutboundTunnels ()) { it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } } @@ -401,12 +405,16 @@ namespace http { s << "Inbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } s << "
\r\n"; s << "Outbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { it->Print(s); + if(it->LatencyIsKnown()) + s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } s << "
\r\n";