diff --git a/AddressBook.h b/AddressBook.h new file mode 100644 index 00000000..6d134247 --- /dev/null +++ b/AddressBook.h @@ -0,0 +1,73 @@ +#ifndef ADDRESS_BOOK_H__ +#define ADDRESS_BOOK_H__ + +#include +#include +#include +#include "base64.h" +#include "util.h" +#include "Identity.h" +#include "Log.h" + +namespace i2p +{ +namespace data +{ + class AddressBook + { + public: + + AddressBook (): m_IsLoaded (false) {}; + + const IdentHash * FindAddress (const std::string& address) + { + if (!m_IsLoaded) + LoadHosts (); + auto it = m_Addresses.find (address); + if (it != m_Addresses.end ()) + return &it->second; + else + return nullptr; + } + + private: + + void LoadHosts () + { + m_IsLoaded = true; + std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ofstream::in); // in text mode + if (!f.is_open ()) + { + LogPrint ("hosts.txt not found"); + return; + } + int numAddresses = 0; + char str[1024]; + while (!f.eof ()) + { + f.getline (str, 1024); + char * key = strchr (str, '='); + if (key) + { + *key = 0; + key++; + Identity ident; + Base64ToByteStream (key, strlen(key), (uint8_t *)&ident, sizeof (ident)); + m_Addresses[str] = CalculateIdentHash (ident); + numAddresses++; + } + } + LogPrint (numAddresses, " addresses loaded"); + } + + private: + + std::map m_Addresses; + bool m_IsLoaded; + }; +} +} + +#endif + + diff --git a/Garlic.cpp b/Garlic.cpp index 32639142..3e336092 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -218,7 +218,7 @@ namespace garlic } GarlicRouting routing; - GarlicRouting::GarlicRouting () + GarlicRouting::GarlicRouting (): m_IsRunning (false), m_Thread (nullptr) { } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index ec655709..c7d1fcaf 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -121,13 +121,16 @@ namespace util s << "Our external address:" << "
" << "
"; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) { - switch (address.transportStyle) { - case i2p::data::RouterInfo::eTransportNTCP: - s << "NTCP  "; + switch (address.transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP: + s << "NTCP  "; break; - case i2p::data::RouterInfo::eTransportSSU: - s << "SSU     "; + case i2p::data::RouterInfo::eTransportSSU: + s << "SSU     "; break; + default: + s << "Unknown  "; } s << address.host.to_string() << ":" << address.port << "
"; } @@ -142,9 +145,11 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) { it.second->GetTunnelConfig ()->Print (s); + if (it.second->GetTunnelPool ()) + s << " " << "Pool"; s << " " << (int)it.second->GetNumReceivedBytes () << "
"; } - + s << "

Transit tunnels

"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) { diff --git a/Identity.h b/Identity.h index 10af227c..6552fa38 100644 --- a/Identity.h +++ b/Identity.h @@ -104,6 +104,13 @@ namespace data mutable i2p::crypto::ElGamalEncryption * m_ElGamalEncryption; // use lazy initialization }; + + class LocalDestination + { + public: + + virtual void UpdateLeaseSet () = 0; // LeaseSet must be update + }; } } diff --git a/Makefile b/Makefile index d9d9b4c9..84a1acc0 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OBJECTS = obj/i2p.o obj/base64.o obj/NTCPSession.o obj/RouterInfo.o obj/Transpor obj/RouterContext.o obj/NetDb.o obj/LeaseSet.o obj/Tunnel.o obj/TunnelEndpoint.o \ obj/TunnelGateway.o obj/TransitTunnel.o obj/I2NPProtocol.o obj/Log.o obj/Garlic.o \ obj/HTTPServer.o obj/Streaming.o obj/Identity.o obj/SSU.o obj/util.o obj/Reseed.o \ - obj/UPnP.o + obj/UPnP.o obj/TunnelPool.o INCFLAGS = LDFLAGS = -Wl,-rpath,/usr/local/lib -lcryptopp -lboost_system -lboost_filesystem -lboost_regex -lboost_program_options -lpthread LIBS = diff --git a/NetDb.cpp b/NetDb.cpp index 1b115c63..951f9623 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -318,13 +318,6 @@ namespace data LogPrint (deletedCount," routers deleted"); } - void NetDb::RequestDestination (const char * b32) - { - uint8_t destination[32]; - Base32ToByteStream (b32, strlen(b32), destination, 32); - RequestDestination (destination, true); - } - void NetDb::RequestDestination (const IdentHash& destination, bool isLeaseSet) { if (isLeaseSet) // we request LeaseSet through tunnels diff --git a/NetDb.h b/NetDb.h index 39f87df0..f9cf3900 100644 --- a/NetDb.h +++ b/NetDb.h @@ -12,6 +12,7 @@ #include "RouterInfo.h" #include "LeaseSet.h" #include "Tunnel.h" +#include "AddressBook.h" namespace i2p { @@ -63,10 +64,10 @@ namespace data void AddLeaseSet (uint8_t * buf, int len); RouterInfo * FindRouter (const IdentHash& ident) const; LeaseSet * FindLeaseSet (const IdentHash& destination) const; + const IdentHash * FindAddress (const std::string& address) { return m_AddressBook.FindAddress (address); }; // TODO: move AddressBook away from NetDb + void Subscribe (const IdentHash& ident); // keep LeaseSets upto date void Unsubscribe (const IdentHash& ident); - - void RequestDestination (const char * b32); // in base32 void RequestDestination (const IdentHash& destination, bool isLeaseSet = false); void HandleDatabaseStoreMsg (uint8_t * buf, size_t len); @@ -104,6 +105,7 @@ namespace data int m_ReseedRetries; std::thread * m_Thread; i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg + AddressBook m_AddressBook; static const char m_NetDbPath[]; }; diff --git a/RouterContext.cpp b/RouterContext.cpp index af88c84a..cb8eb355 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -76,49 +76,27 @@ namespace i2p bool RouterContext::Load () { - std::string dataDir = i2p::util::filesystem::GetDataDir ().string (); -#ifndef _WIN32 - dataDir.append ("/"); -#else - dataDir.append ("\\"); -#endif - std::string router_keys = dataDir; - router_keys.append (ROUTER_KEYS); - std::string router_info = dataDir; - router_info.append (ROUTER_INFO); - - std::ifstream fk (router_keys.c_str (), std::ifstream::binary | std::ofstream::in); + std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ofstream::in); if (!fk.is_open ()) return false; fk.read ((char *)&m_Keys, sizeof (m_Keys)); m_SigningPrivateKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, CryptoPP::Integer (m_Keys.signingPrivateKey, 20)); - m_RouterInfo = i2p::data::RouterInfo (router_info.c_str ()); // TODO + m_RouterInfo = i2p::data::RouterInfo (i2p::util::filesystem::GetFullPath (ROUTER_INFO).c_str ()); // TODO return true; } void RouterContext::Save (bool infoOnly) { - std::string dataDir = i2p::util::filesystem::GetDataDir ().string (); -#ifndef _WIN32 - dataDir.append ("/"); -#else - dataDir.append ("\\"); -#endif - std::string router_keys = dataDir; - router_keys.append (ROUTER_KEYS); - std::string router_info = dataDir; - router_info.append (ROUTER_INFO); - if (!infoOnly) { - std::ofstream fk (router_keys.c_str (), std::ofstream::binary | std::ofstream::out); + std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out); fk.write ((char *)&m_Keys, sizeof (m_Keys)); } - std::ofstream fi (router_info.c_str (), std::ofstream::binary | std::ofstream::out); + std::ofstream fi (i2p::util::filesystem::GetFullPath (ROUTER_INFO).c_str (), std::ofstream::binary | std::ofstream::out); fi.write ((char *)m_RouterInfo.GetBuffer (), m_RouterInfo.GetBufferLen ()); } } diff --git a/SSU.cpp b/SSU.cpp index 7efd14e4..196c0a72 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -853,8 +853,9 @@ namespace ssu auto it = m_Sessions.find (oldEndpoint); if (it != m_Sessions.end ()) { + auto session = it->second; m_Sessions.erase (it); - m_Sessions[newEndpoint] = it->second; + m_Sessions[newEndpoint] = session; LogPrint ("SSU session ressigned from ", oldEndpoint.address ().to_string (), ":", oldEndpoint.port (), " to ", newEndpoint.address ().to_string (), ":", newEndpoint.port ()); } diff --git a/Streaming.cpp b/Streaming.cpp index 6caa2a0f..b7282145 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -308,14 +308,17 @@ namespace stream m_IdentHash = i2p::data::CalculateIdentHash (m_Identity); m_SigningPrivateKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, CryptoPP::Integer (m_Keys.signingPrivateKey, 20)); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this); } StreamingDestination::~StreamingDestination () { if (m_LeaseSet) DeleteI2NPMessage (m_LeaseSet); + if (m_Pool) + i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); } - + void StreamingDestination::HandleNextPacket (Packet * packet) { uint32_t sendStreamID = packet->GetSendStreamID (); @@ -345,12 +348,20 @@ namespace stream } } - I2NPMessage * StreamingDestination::GetLeaseSet () + void StreamingDestination::UpdateLeaseSet () { - if (m_LeaseSet) // temporary always create new LeaseSet - DeleteI2NPMessage (m_LeaseSet); - m_LeaseSet = CreateLeaseSet (); + auto newLeaseSet = CreateLeaseSet (); + // TODO: make it atomic + auto oldLeaseSet = m_LeaseSet; + m_LeaseSet = newLeaseSet; + if (oldLeaseSet) + DeleteI2NPMessage (oldLeaseSet); + } + I2NPMessage * StreamingDestination::GetLeaseSet () + { + if (!m_LeaseSet) + m_LeaseSet = CreateLeaseSet (); return m_LeaseSet; } @@ -370,7 +381,7 @@ namespace stream size += 256; // encryption key memset (buf + size, 0, 128); size += 128; // signing key - auto tunnels = i2p::tunnel::tunnels.GetInboundTunnels (5); // 5 tunnels maximum + auto tunnels = m_Pool->GetInboundTunnels (5); // 5 tunnels maximum buf[size] = tunnels.size (); // num leases size++; // num for (auto it: tunnels) @@ -411,6 +422,17 @@ namespace stream if (sharedLocalDestination) sharedLocalDestination->DeleteStream (stream); } + + void StartStreaming () + { + if (!sharedLocalDestination) + sharedLocalDestination = new StreamingDestination (); + } + + void StopStreaming () + { + delete sharedLocalDestination; + } void HandleDataMessage (i2p::data::IdentHash * destination, const uint8_t * buf, size_t len) { diff --git a/Streaming.h b/Streaming.h index 4e33d111..8f0f289a 100644 --- a/Streaming.h +++ b/Streaming.h @@ -11,6 +11,7 @@ #include "LeaseSet.h" #include "I2NPProtocol.h" #include "Tunnel.h" +#include "TunnelPool.h" namespace i2p { @@ -97,22 +98,25 @@ namespace stream i2p::tunnel::OutboundTunnel * m_OutboundTunnel; }; - class StreamingDestination + class StreamingDestination: public i2p::data::LocalDestination { public: StreamingDestination (); - ~StreamingDestination (); - + ~StreamingDestination (); + const i2p::data::Keys& GetKeys () const { return m_Keys; }; const i2p::data::Identity& GetIdentity () const { return m_Identity; }; I2NPMessage * GetLeaseSet (); - void Sign (uint8_t * buf, int len, uint8_t * signature) const; - + void Sign (uint8_t * buf, int len, uint8_t * signature) const; + Stream * CreateNewStream (const i2p::data::LeaseSet& remote); void DeleteStream (Stream * stream); void HandleNextPacket (Packet * packet); + // implements LocalDestination + void UpdateLeaseSet (); + private: I2NPMessage * CreateLeaseSet () const; @@ -124,6 +128,7 @@ namespace stream i2p::data::Identity m_Identity; i2p::data::IdentHash m_IdentHash; + i2p::tunnel::TunnelPool * m_Pool; I2NPMessage * m_LeaseSet; CryptoPP::DSA::PrivateKey m_SigningPrivateKey; @@ -131,6 +136,8 @@ namespace stream Stream * CreateStream (const i2p::data::LeaseSet& remote); void DeleteStream (Stream * stream); + void StartStreaming (); + void StopStreaming (); // assuming data is I2CP message void HandleDataMessage (i2p::data::IdentHash * destination, const uint8_t * buf, size_t len); diff --git a/Tunnel.cpp b/Tunnel.cpp index ed9708cb..e9e59b86 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -14,7 +14,7 @@ namespace i2p namespace tunnel { - Tunnel::Tunnel (TunnelConfig * config): m_Config (config), m_IsEstablished (false) + Tunnel::Tunnel (TunnelConfig * config): m_Config (config), m_Pool (nullptr), m_IsEstablished (false) { } @@ -186,6 +186,10 @@ namespace tunnel for (auto& it : m_PendingTunnels) delete it.second; m_PendingTunnels.clear (); + + for (auto& it: m_Pools) + delete it; + m_Pools.clear (); } InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID) @@ -268,6 +272,19 @@ namespace tunnel } return tunnel;*/ } + + TunnelPool * Tunnels::CreateTunnelPool (i2p::data::LocalDestination * localDestination) + { + auto pool = new TunnelPool (localDestination); + m_Pools.push_back (pool); + return pool; + } + + void Tunnels::DeleteTunnelPool (TunnelPool * pool) + { + m_Pools.remove (pool); + delete pool; + } void Tunnels::AddTransitTunnel (TransitTunnel * tunnel) { @@ -350,6 +367,7 @@ namespace tunnel ManageInboundTunnels (); ManageOutboundTunnels (); ManageTransitTunnels (); + ManageTunnelPools (); /* if (!m_IsTunnelCreated) { @@ -418,6 +436,9 @@ namespace tunnel if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint ("Tunnel ", it->second->GetTunnelID (), " expired"); + auto pool = it->second->GetTunnelPool (); + if (pool) + pool->TunnelExpired (it->second); it = m_InboundTunnels.erase (it); } else @@ -431,7 +452,7 @@ namespace tunnel return; } - if (m_InboundTunnels.size () < 10) + if (m_InboundTunnels.size () < 15) // TODO: store exploratory tunnels explicitly { // trying to create one more inbound tunnel if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) @@ -474,6 +495,12 @@ namespace tunnel it++; } } + + void Tunnels::ManageTunnelPools () + { + for (auto& it: m_Pools) + it->CreateTunnels (); + } void Tunnels::PostTunnelData (I2NPMessage * msg) { @@ -498,8 +525,14 @@ namespace tunnel void Tunnels::AddInboundTunnel (InboundTunnel * newTunnel) { m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; - // build symmetric outbound tunnel - CreateTunnel (newTunnel->GetTunnelConfig ()->Invert (), GetNextOutboundTunnel ()); + auto pool = newTunnel->GetTunnelPool (); + if (!pool) + { + // build symmetric outbound tunnel + CreateTunnel (newTunnel->GetTunnelConfig ()->Invert (), GetNextOutboundTunnel ()); + } + else + pool->TunnelCreated (newTunnel); } diff --git a/Tunnel.h b/Tunnel.h index 7e7f94d5..4374c631 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -11,6 +11,7 @@ #include #include "Queue.h" #include "TunnelConfig.h" +#include "TunnelPool.h" #include "TransitTunnel.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" @@ -36,7 +37,9 @@ namespace tunnel TunnelConfig * GetTunnelConfig () const { return m_Config; } bool IsEstablished () const { return m_IsEstablished; }; - + TunnelPool * GetTunnelPool () const { return m_Pool; }; + void SetTunnelPool (TunnelPool * pool) { m_Pool = pool; }; + bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase @@ -53,6 +56,7 @@ namespace tunnel private: TunnelConfig * m_Config; + TunnelPool * m_Pool; // pool, tunnel belongs to, or null bool m_IsEstablished; CryptoPP::ECB_Mode::Decryption m_ECBDecryption; @@ -101,9 +105,8 @@ namespace tunnel Tunnels (); ~Tunnels (); - void Start (); - void Stop (); + void Stop (); InboundTunnel * GetInboundTunnel (uint32_t tunnelID); Tunnel * GetPendingTunnel (uint32_t replyMsgID); @@ -117,6 +120,8 @@ namespace tunnel void PostTunnelData (I2NPMessage * msg); template TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); + TunnelPool * CreateTunnelPool (i2p::data::LocalDestination * localDestination); + void DeleteTunnelPool (TunnelPool * pool); OutboundTunnel * CreateOneHopOutboundTestTunnel (InboundTunnel * replyTunnel); InboundTunnel * CreateOneHopInboundTestTunnel (OutboundTunnel * outboundTunnel = 0); @@ -130,6 +135,7 @@ namespace tunnel void ManageOutboundTunnels (); void ManageInboundTunnels (); void ManageTransitTunnels (); + void ManageTunnelPools (); void CreateZeroHopsInboundTunnel (); @@ -143,6 +149,7 @@ namespace tunnel std::map m_InboundTunnels; std::list m_OutboundTunnels; std::map m_TransitTunnels; + std::list m_Pools; i2p::util::Queue m_Queue; public: diff --git a/TunnelBase.h b/TunnelBase.h index 6be902ca..24704d46 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -48,6 +48,17 @@ namespace tunnel uint32_t m_CreationTime; // seconds since epoch }; + + struct TunnelCreationTimeCmp + { + bool operator() (const TunnelBase * t1, const TunnelBase * t2) const + { + if (t1->GetCreationTime () != t2->GetCreationTime ()) + return t1->GetCreationTime () > t2->GetCreationTime (); + else + return t1 < t2; + }; + }; } } diff --git a/TunnelPool.cpp b/TunnelPool.cpp new file mode 100644 index 00000000..d9d38354 --- /dev/null +++ b/TunnelPool.cpp @@ -0,0 +1,73 @@ +#include "Tunnel.h" +#include "NetDb.h" +#include "Timestamp.h" +#include "TunnelPool.h" + +namespace i2p +{ +namespace tunnel +{ + TunnelPool::TunnelPool (i2p::data::LocalDestination * owner, int numTunnels): + m_Owner (owner), m_NumTunnels (numTunnels) + { + } + + TunnelPool::~TunnelPool () + { + for (auto it: m_InboundTunnels) + it->SetTunnelPool (nullptr); + } + + void TunnelPool::TunnelCreated (InboundTunnel * createdTunnel) + { + m_InboundTunnels.insert (createdTunnel); + if (m_Owner) + m_Owner->UpdateLeaseSet (); + } + + void TunnelPool::TunnelExpired (InboundTunnel * expiredTunnel) + { + m_InboundTunnels.erase (expiredTunnel); + if (m_Owner) + m_Owner->UpdateLeaseSet (); + } + + std::vector TunnelPool::GetInboundTunnels (int num) const + { + std::vector v; + int i = 0; + for (auto it : m_InboundTunnels) + { + if (i >= num) break; + v.push_back (it); + i++; + } + return v; + } + + void TunnelPool::CreateTunnels () + { + int num = m_InboundTunnels.size (); + for (int i = num; i < m_NumTunnels; i++) + CreateInboundTunnel (); + } + + void TunnelPool::CreateInboundTunnel () + { + OutboundTunnel * outboundTunnel = tunnels.GetNextOutboundTunnel (); + LogPrint ("Creating destination inbound tunnel..."); + auto firstHop = i2p::data::netdb.GetRandomRouter (outboundTunnel ? outboundTunnel->GetEndpointRouter () : nullptr); + auto secondHop = i2p::data::netdb.GetRandomRouter (firstHop); + auto * tunnel = tunnels.CreateTunnel ( + new TunnelConfig (std::vector + { + firstHop, + secondHop + // TODO: swithc to 3-hops later + /*i2p::data::netdb.GetRandomRouter (secondHop) */ + }), + outboundTunnel); + tunnel->SetTunnelPool (this); + } +} +} diff --git a/TunnelPool.h b/TunnelPool.h new file mode 100644 index 00000000..988e83c0 --- /dev/null +++ b/TunnelPool.h @@ -0,0 +1,46 @@ +#ifndef TUNNEL_POOL__ +#define TUNNEL_POOL__ + +#include +#include +#include "Identity.h" +#include "LeaseSet.h" +#include "I2NPProtocol.h" +#include "TunnelBase.h" + +namespace i2p +{ +namespace tunnel +{ + class Tunnel; + class InboundTunnel; + class OutboundTunnel; + + class TunnelPool // per local destination + { + public: + + TunnelPool (i2p::data::LocalDestination * owner, int numTunnels = 5); + ~TunnelPool (); + + void CreateTunnels (); + void TunnelCreated (InboundTunnel * createdTunnel); + void TunnelExpired (InboundTunnel * expiredTunnel); + std::vector GetInboundTunnels (int num) const; + + private: + + void CreateInboundTunnel (); + + private: + + i2p::data::LocalDestination * m_Owner; + int m_NumTunnels; + std::set m_InboundTunnels; // recent tunnel appears first + + }; +} +} + +#endif + diff --git a/i2p.cpp b/i2p.cpp index 839e7c01..b13b1791 100644 --- a/i2p.cpp +++ b/i2p.cpp @@ -24,6 +24,7 @@ #include "HTTPServer.h" #include "Garlic.h" #include "util.h" +#include "Streaming.h" // Global @@ -154,6 +155,7 @@ int main( int argc, char* argv[] ) i2p::transports.Start (); i2p::tunnel::tunnels.Start (); i2p::garlic::routing.Start (); + i2p::stream::StartStreaming (); while (running) { @@ -162,6 +164,7 @@ int main( int argc, char* argv[] ) } LogPrint("Shutdown started."); + i2p::stream::StopStreaming (); i2p::garlic::routing.Stop (); i2p::tunnel::tunnels.Stop (); i2p::transports.Stop (); diff --git a/util.cpp b/util.cpp index 262b6e25..ef4c2e86 100644 --- a/util.cpp +++ b/util.cpp @@ -121,6 +121,18 @@ namespace filesystem return path; } + std::string GetFullPath (const std::string& filename) + { + std::string fullPath = GetDataDir ().string (); +#ifndef _WIN32 + fullPath.append ("/"); +#else + fullPath.append ("\\"); +#endif + fullPath.append (filename); + return fullPath; + } + boost::filesystem::path GetConfigFile() { boost::filesystem::path pathConfigFile(i2p::util::config::GetArg("-conf", "i2p.conf")); diff --git a/util.h b/util.h index 519e51b3..6bd367e2 100644 --- a/util.h +++ b/util.h @@ -25,6 +25,7 @@ namespace util namespace filesystem { const boost::filesystem::path &GetDataDir(); + std::string GetFullPath (const std::string& filename); boost::filesystem::path GetDefaultDataDir(); boost::filesystem::path GetConfigFile(); void ReadConfigFile(std::map& mapSettingsRet,