diff --git a/.gitignore b/.gitignore index b9d6bd92..0e609a70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +# i2pd +obj/*.o +router.info +router.keys +i2p +netDb + ################# ## Eclipse ################# diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 502a2e40..6503d70f 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -386,15 +386,13 @@ namespace i2p TunnelGatewayHeader * header = (TunnelGatewayHeader *)msg->GetPayload (); uint32_t tunnelID = be32toh(header->tunnelID); uint16_t len = be16toh(header->length); - LogPrint ("TunnelGateway of ", (int)len, " bytes for tunnel ", (unsigned int)tunnelID); + // we make payload as new I2NP message to send + msg->offset += sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader); + msg->len = msg->offset + len; + LogPrint ("TunnelGateway of ", (int)len, " bytes for tunnel ", (unsigned int)tunnelID, ". Msg type ", (int)msg->GetHeader()->typeID); i2p::tunnel::TransitTunnel * tunnel = i2p::tunnel::tunnels.GetTransitTunnel (tunnelID); if (tunnel) - { - // we make payload as new I2NP message to send - msg->offset += sizeof (I2NPHeader) + sizeof (TunnelGatewayHeader); - msg->len = msg->offset + len; tunnel->SendTunnelDataMsg (nullptr, 0, msg); - } else { LogPrint ("Tunnel ", (unsigned int)tunnelID, " not found"); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index efa6409f..2b75b463 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -3,6 +3,7 @@ #include "CryptoConst.h" #include "Log.h" #include "Timestamp.h" +#include "NetDb.h" #include "LeaseSet.h" namespace i2p @@ -28,6 +29,7 @@ namespace data memcpy (m_EncryptionKey, header->encryptionKey, 256); LogPrint ("LeaseSet num=", (int)header->num); + // process leases const uint8_t * leases = buf + sizeof (H); for (int i = 0; i < header->num; i++) { @@ -36,8 +38,16 @@ namespace data lease.endDate = be64toh (lease.endDate); m_Leases.push_back (lease); leases += sizeof (Lease); - } + // check if lease's gateway is in our netDb + if (!netdb.FindRouter (lease.tunnelGateway)) + { + // if not found request it + LogPrint ("Lease's tunnel gateway not found. Requested"); + netdb.RequestDestination (lease.tunnelGateway); + } + } + // verify CryptoPP::DSA::PublicKey pubKey; pubKey.Initialize (i2p::crypto::dsap, i2p::crypto::dsaq, i2p::crypto::dsag, diff --git a/Makefile b/Makefile index 84c7a32f..b5739c46 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,31 @@ CC = g++ CFLAGS = -g -Wall -std=c++0x -OBJECTS = i2p.o base64.o NTCPSession.o RouterInfo.o Transports.o RouterContext.o \ - NetDb.o LeaseSet.o Tunnel.o TunnelEndpoint.o TunnelGateway.o TransitTunnel.o \ - I2NPProtocol.o Log.o Garlic.o HTTPServer.o Streaming.o Identity.o SSU.o +OBJECTS = obj/i2p.o obj/base64.o obj/NTCPSession.o obj/RouterInfo.o obj/Transports.o \ + 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 INCFLAGS = -LDFLAGS = -Wl,-rpath,/usr/local/lib -lcryptopp -lboost_system -lboost_filesystem +LDFLAGS = -Wl,-rpath,/usr/local/lib -lcryptopp -lboost_system -lboost_filesystem -lboost_regex -lboost_program_options -lpthread LIBS = -all: i2p +all: obj i2p -i2p: $(OBJECTS) - $(CC) -o i2p $(OBJECTS) $(LDFLAGS) $(LIBS) +i2p: $(OBJECTS:obj/%=obj/%) + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) .SUFFIXES: .SUFFIXES: .c .cc .C .cpp .o -.cpp.o : - $(CC) -o $@ -c $(CFLAGS) $< $(INCFLAGS) +obj/%.o : %.cpp + $(CC) -o $@ $< -c $(CFLAGS) $(INCFLAGS) + +obj: + mkdir -p obj clean: - rm -f *.o + rm -fr obj i2p .PHONY: all .PHONY: clean + diff --git a/NetDb.cpp b/NetDb.cpp index 75669260..01234ad7 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -2,22 +2,23 @@ #include #include #include -#include #include #include "base64.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Tunnel.h" +#include "Transports.h" #include "RouterContext.h" #include "Garlic.h" #include "NetDb.h" +#include "Reseed.h" +#include "util.h" namespace i2p { namespace data { - I2NPMessage * RequestedDestination::CreateRequestMessage (const RouterInfo * router, const i2p::tunnel::InboundTunnel * replyTunnel) { @@ -30,10 +31,25 @@ namespace data m_LastReplyTunnel = replyTunnel; return msg; } - + + I2NPMessage * RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) + { + I2NPMessage * msg = i2p::CreateDatabaseLookupMsg (m_Destination, + i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); + m_ExcludedPeers.insert (floodfill); + m_LastRouter = nullptr; + m_LastReplyTunnel = nullptr; + return msg; + } + +#ifndef _WIN32 + const char NetDb::m_NetDbPath[] = "/netDb"; +#else + const char NetDb::m_NetDbPath[] = "\\netDb"; +#endif NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (0) + NetDb::NetDb (): m_IsRunning (false), m_ReseedRetries (0), m_Thread (0) { } @@ -49,8 +65,15 @@ namespace data } void NetDb::Start () - { - Load ("netDb"); + { + Load (m_NetDbPath); + while (m_RouterInfos.size () < 100 && m_ReseedRetries < 10) + { + Reseeder reseeder; + reseeder.reseedNow(); + m_ReseedRetries++; + Load (m_NetDbPath); + } m_Thread = new std::thread (std::bind (&NetDb::Run, this)); } @@ -100,7 +123,7 @@ namespace data if (ts - lastTs >= 60) // save routers every minute { if (lastTs) - SaveUpdated ("netDb"); + SaveUpdated (m_NetDbPath); lastTs = ts; } } @@ -166,46 +189,90 @@ namespace data return it->second; else return nullptr; - } - + } + + // TODO: Move to reseed and/or scheduled tasks. (In java version, scheduler fix this as well as sort RIs.) + bool NetDb::CreateNetDb(boost::filesystem::path directory) + { + LogPrint (directory.string(), " doesn't exist, trying to create it."); + if (!boost::filesystem::create_directory (directory)) + { + LogPrint("Failed to create directory ", directory.string()); + return false; + } + + // list of chars might appear in base64 string + const char * chars = GetBase64SubstitutionTable (); // 64 bytes + boost::filesystem::path suffix; + for (int i = 0; i < 64; i++) + { +#ifndef _WIN32 + suffix = std::string ("/r") + chars[i]; +#else + suffix = std::string ("\\r") + chars[i]; +#endif + if (!boost::filesystem::create_directory( boost::filesystem::path (directory / suffix) )) return false; + } + return true; + } + void NetDb::Load (const char * directory) { - boost::filesystem::path p (directory); - if (boost::filesystem::exists (p)) + boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); + p /= (directory); + if (!boost::filesystem::exists (p)) { - int numRouters = 0; - boost::filesystem::directory_iterator end; - for (boost::filesystem::directory_iterator it (p); it != end; ++it) + // seems netDb doesn't exist yet + if (!CreateNetDb(p)) return; + } + // make sure we cleanup netDb from previous attempts + for (auto r: m_RouterInfos) + delete r.second; + m_RouterInfos.clear (); + + // load routers now + int numRouters = 0; + boost::filesystem::directory_iterator end; + for (boost::filesystem::directory_iterator it (p); it != end; ++it) + { + if (boost::filesystem::is_directory (it->status())) { - if (boost::filesystem::is_directory (it->status())) + for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1) { - for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1) - { #if BOOST_VERSION > 10500 - RouterInfo * r = new RouterInfo (it1->path().string().c_str ()); + RouterInfo * r = new RouterInfo (it1->path().string().c_str ()); #else - RouterInfo * r = new RouterInfo(it1->path().c_str()); + RouterInfo * r = new RouterInfo(it1->path().c_str()); #endif - m_RouterInfos[r->GetIdentHash ()] = r; - numRouters++; - } + m_RouterInfos[r->GetIdentHash ()] = r; + numRouters++; } } - LogPrint (numRouters, " routers loaded"); } - else - LogPrint (directory, " doesn't exist"); + LogPrint (numRouters, " routers loaded"); } void NetDb::SaveUpdated (const char * directory) { auto GetFilePath = [](const char * directory, const RouterInfo * routerInfo) { +#ifndef _WIN32 return std::string (directory) + "/r" + - routerInfo->GetIdentHashBase64 ()[0] + "/routerInfo-" + + routerInfo->GetIdentHashBase64 ()[0] + "/routerInfo-" + +#else + return std::string (directory) + "\\r" + + routerInfo->GetIdentHashBase64 ()[0] + "\\routerInfo-" + +#endif routerInfo->GetIdentHashBase64 () + ".dat"; }; - + + boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); + p /= (directory); +#if BOOST_VERSION > 10500 + const char * fullDirectory = p.string().c_str (); +#else + const char * fullDirectory = p.c_str (); +#endif int count = 0, deletedCount = 0; auto total = m_RouterInfos.size (); uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -213,7 +280,7 @@ namespace data { if (it.second->IsUpdated ()) { - std::ofstream r (GetFilePath(directory, it.second), std::ofstream::binary); + std::ofstream r (GetFilePath(fullDirectory, it.second), std::ofstream::binary); r.write ((char *)it.second->GetBuffer (), it.second->GetBufferLen ()); it.second->SetUpdated (false); count++; @@ -229,9 +296,9 @@ namespace data if (it.second->IsUnreachable ()) { - if (boost::filesystem::exists (GetFilePath (directory, it.second))) + if (boost::filesystem::exists (GetFilePath (fullDirectory, it.second))) { - boost::filesystem::remove (GetFilePath (directory, it.second)); + boost::filesystem::remove (GetFilePath (fullDirectory, it.second)); deletedCount++; } } @@ -252,44 +319,49 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, bool isLeaseSet) { - i2p::tunnel::OutboundTunnel * outbound = i2p::tunnel::tunnels.GetNextOutboundTunnel (); - if (outbound) - { - i2p::tunnel::InboundTunnel * inbound = i2p::tunnel::tunnels.GetNextInboundTunnel (); - if (inbound) + if (isLeaseSet) // we request LeaseSet through tunnels + { + i2p::tunnel::OutboundTunnel * outbound = i2p::tunnel::tunnels.GetNextOutboundTunnel (); + if (outbound) { - RequestedDestination * dest = CreateRequestedDestination (destination, isLeaseSet); - auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); - if (floodfill) - { - std::vector msgs; - // our RouterInfo - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - floodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg () - }); - - // DatabaseLookup message - dest->SetLastOutboundTunnel (outbound); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - floodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (floodfill, inbound) - }); + i2p::tunnel::InboundTunnel * inbound = i2p::tunnel::tunnels.GetNextInboundTunnel (); + if (inbound) + { + RequestedDestination * dest = CreateRequestedDestination (destination, isLeaseSet); + auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); + if (floodfill) + { + std::vector msgs; + // DatabaseLookup message + dest->SetLastOutboundTunnel (outbound); + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeRouter, + floodfill->GetIdentHash (), 0, + dest->CreateRequestMessage (floodfill, inbound) + }); - outbound->SendTunnelDataMsg (msgs); + outbound->SendTunnelDataMsg (msgs); + } + else + LogPrint ("No more floodfills found"); } else - LogPrint ("No more floodfills found"); - } + LogPrint ("No inbound tunnels found"); + } else - LogPrint ("No inbound tunnels found"); - } - else - LogPrint ("No outbound tunnels found"); + LogPrint ("No outbound tunnels found"); + } + else // RouterInfo is requested directly + { + RequestedDestination * dest = CreateRequestedDestination (destination, false); + auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); + if (floodfill) + { + dest->SetLastOutboundTunnel (nullptr); + i2p::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); + } + } } void NetDb::HandleDatabaseStoreMsg (uint8_t * buf, size_t len) @@ -402,11 +474,18 @@ namespace data dest->GetLastRouter ()->GetIdentHash (), 0, msg }); } + } + else // we should send directly + { + if (!dest->IsLeaseSet ()) // if not LeaseSet + i2p::transports.SendMessage (router, dest->CreateRequestMessage (router)); + else + LogPrint ("Can't request LeaseSet"); } } } - if (msgs.size () > 0) + if (outbound && msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); } else @@ -427,7 +506,7 @@ namespace data auto inbound = i2p::tunnel::tunnels.GetNextInboundTunnel (); if (outbound && inbound) { - auto floodfill = GetRandomNTCPRouter (true); + auto floodfill = GetRandomRouter (outbound->GetEndpointRouter (), true); if (floodfill) { LogPrint ("Exploring new routers ..."); @@ -495,19 +574,29 @@ namespace data return last; } - const RouterInfo * NetDb::GetRandomRouter () const + const RouterInfo * NetDb::GetRandomRouter (const RouterInfo * compatibleWith, bool floodfillOnly) const { CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - uint32_t ind = rnd.GenerateWord32 (0, m_RouterInfos.size () - 1), i = 0; - RouterInfo * last = nullptr; - for (auto it: m_RouterInfos) + uint32_t ind = rnd.GenerateWord32 (0, m_RouterInfos.size () - 1); + for (int j = 0; j < 2; j++) { - if (!it.second->IsUnreachable ()) - last = it.second; - if (i >= ind) break; - else i++; + uint32_t i = 0; + for (auto it: m_RouterInfos) + { + if (i >= ind) + { + if (!it.second->IsUnreachable () && + (!compatibleWith || it.second->IsCompatible (*compatibleWith)) && + (!floodfillOnly || it.second->IsFloodfill ())) + return it.second; + } + else + i++; + } + // we couldn't find anything, try second pass + ind = 0; } - return last; + return nullptr; // seem we have too few routers } void NetDb::PostI2NPMsg (I2NPMessage * msg) @@ -537,41 +626,5 @@ namespace data return r; } - void NetDb::DownloadRouterInfo (const std::string& address, const std::string& filename) - { - try - { - boost::asio::ip::tcp::iostream site(address, "http"); - if (!site) - { - //site.expires_from_now (boost::posix_time::seconds (10)); // wait for 10 seconds - site << "GET " << filename << "HTTP/1.0\nHost: " << address << "\nAccept: */*\nConnection: close\n\n"; - // read response - std::string version, statusMessage; - site >> version; // HTTP version - int status; - site >> status; // status - std::getline (site, statusMessage); - if (status == 200) // OK - { - std::string header; - while (header != "\n") - std::getline (site, header); - // read content - std::stringstream ss; - ss << site.rdbuf(); - AddRouterInfo ((uint8_t *)ss.str ().c_str (), ss.str ().size ()); - } - else - LogPrint ("HTTP response ", status); - } - else - LogPrint ("Can't connect to ", address); - } - catch (std::exception& ex) - { - LogPrint ("Failed to download ", filename, " : ", ex.what ()); - } - } } } diff --git a/NetDb.h b/NetDb.h index 03be5ed6..bcf1f23c 100644 --- a/NetDb.h +++ b/NetDb.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Queue.h" #include "I2NPProtocol.h" #include "RouterInfo.h" @@ -30,9 +31,11 @@ namespace data const RouterInfo * GetLastRouter () const { return m_LastRouter; }; const i2p::tunnel::InboundTunnel * GetLastReplyTunnel () const { return m_LastReplyTunnel; }; bool IsExploratory () const { return m_IsExploratory; }; + bool IsLeaseSet () const { return m_IsLeaseSet; }; bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; I2NPMessage * CreateRequestMessage (const RouterInfo * router, const i2p::tunnel::InboundTunnel * replyTunnel); - + I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); + i2p::tunnel::OutboundTunnel * GetLastOutboundTunnel () const { return m_LastOutboundTunnel; }; void SetLastOutboundTunnel (i2p::tunnel::OutboundTunnel * tunnel) { m_LastOutboundTunnel = tunnel; }; @@ -68,15 +71,15 @@ namespace data void HandleDatabaseSearchReplyMsg (I2NPMessage * msg); const RouterInfo * GetRandomNTCPRouter (bool floodfillOnly = false) const; - const RouterInfo * GetRandomRouter () const; + const RouterInfo * GetRandomRouter (const RouterInfo * compatibleWith = nullptr, bool floodfillOnly = false) const; void PostI2NPMsg (I2NPMessage * msg); private: + bool CreateNetDb(boost::filesystem::path directory); void Load (const char * directory); void SaveUpdated (const char * directory); - void DownloadRouterInfo (const std::string& address, const std::string& filename); // for reseed void Run (); // exploratory thread void Explore (); const RouterInfo * GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; @@ -92,8 +95,11 @@ namespace data std::map m_RequestedDestinations; bool m_IsRunning; + int m_ReseedRetries; std::thread * m_Thread; i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg + + static const char m_NetDbPath[]; }; extern NetDb netdb; diff --git a/README.md b/README.md index ab64cb4d..dc4e0a8c 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,32 @@ Requires gcc 4.6 and higher, boost 1.46 and higher, crypto++ on Windows Requires msvs2013, boost 1.46 and higher, crypto++ + + +Testing +------- + +First, build it. + +* $ cd i2pd +* $ make + +Next, find out your public ip. (find it for example at http://www.whatismyip.com/) + +Then, run it with: + +$ ./i2p --host=YOUR_PUBLIC_IP + +The client should now reseed by itself. + +Other options: +* --port= - The port to listen on +* --httpport= - The http port to listen on + + +To visit an I2P page, you need to find the b32 address of your destination. +After that, go to the webconsole and add it behind the url. (Remove http:// and b32.i2p from the address) + +This should resulting in for example: +http://localhost:7070/4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa + diff --git a/Reseed.cpp b/Reseed.cpp new file mode 100644 index 00000000..e60ffa44 --- /dev/null +++ b/Reseed.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include "Reseed.h" +#include "Log.h" +#include "util.h" + + +namespace i2p +{ +namespace data +{ + + static std::vector httpReseedHostList = { + "http://193.150.121.66/netDb/", + "http://netdb.i2p2.no/", + "http://reseed.i2p-projekt.de/", + "http://cowpuncher.drollette.com/netdb/", + "http://i2p.mooo.com/netDb/", + "http://reseed.info/", + "http://reseed.pkol.de/", + "http://uk.reseed.i2p2.no/", + "http://i2p-netdb.innovatio.no/", + "http://ieb9oopo.mooo.com" + }; + + //TODO: Implement v2 reseeding. Lightweight zip library is needed. + //TODO: Implement SU3, utils. + Reseeder::Reseeder() + { + } + + Reseeder::~Reseeder() + { + } + + bool Reseeder::reseedNow() + { + try + { + std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())]; + LogPrint("Reseeding from ", reseedHost); + std::string content = i2p::util::http::httpRequest(reseedHost); + if (content == "") + { + LogPrint("Reseed failed"); + return false; + } + boost::regex e("<\\s*A\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"", boost::regex::normal | boost::regbase::icase); + boost::sregex_token_iterator i(content.begin(), content.end(), e, 1); + boost::sregex_token_iterator j; + //TODO: Ugly code, try to clean up. + //TODO: Try to reduce N number of variables + std::string name; + std::string routerInfo; + std::string tmpUrl; + std::string filename; + std::string ignoreFileSuffix = ".zip"; + boost::filesystem::path root = i2p::util::filesystem::GetDataDir(); + while (i != j) + { + name = *i++; + if (name.find(ignoreFileSuffix)!=std::string::npos) + continue; + LogPrint("Downloading ", name); + tmpUrl = reseedHost; + tmpUrl.append(name); + routerInfo = i2p::util::http::httpRequest(tmpUrl); + if (routerInfo.size()==0) + continue; + filename = root.string(); +#ifndef _WIN32 + filename += "/netDb/r"; +#else + filename += "\\netDb\\r"; +#endif + filename += name.at(11); // first char in id +#ifndef _WIN32 + filename.append("/"); +#else + filename.append("\\"); +#endif + filename.append(name.c_str()); + std::ofstream outfile (filename, std::ios::binary); + outfile << routerInfo; + outfile.close(); + } + return true; + } + catch (std::exception& ex) + { + //TODO: error reporting + return false; + } + return false; + } + +} +} + diff --git a/Reseed.h b/Reseed.h new file mode 100644 index 00000000..3b07d840 --- /dev/null +++ b/Reseed.h @@ -0,0 +1,23 @@ +#ifndef RESEED_H +#define RESEED_H + +#include +#include + +namespace i2p +{ +namespace data +{ + + class Reseeder + { + public: + Reseeder(); + ~Reseeder(); + bool reseedNow(); + }; + +} +} + +#endif \ No newline at end of file diff --git a/RouterContext.cpp b/RouterContext.cpp index 75a3eb51..68f6508a 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -3,6 +3,7 @@ #include #include "CryptoConst.h" #include "RouterContext.h" +#include "util.h" namespace i2p { diff --git a/RouterInfo.cpp b/RouterInfo.cpp index ee9a60c2..3b0054a0 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -13,19 +13,18 @@ #include "RouterContext.h" - namespace i2p { namespace data { RouterInfo::RouterInfo (const char * filename): - m_IsUpdated (false), m_IsUnreachable (false) + m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0) { ReadFromFile (filename); } RouterInfo::RouterInfo (const uint8_t * buf, int len): - m_IsUpdated (true) + m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0) { memcpy (m_Buffer, buf, len); m_BufferLen = len; @@ -47,7 +46,12 @@ namespace data if (s.is_open ()) { s.seekg (0,std::ios::end); - m_BufferLen = s.tellg (); + m_BufferLen = s.tellg (); + if (m_BufferLen < 40) + { + LogPrint("File", filename, " is malformed"); + return; + } s.seekg(0, std::ios::beg); s.read(m_Buffer,m_BufferLen); ReadFromBuffer (); @@ -111,10 +115,20 @@ namespace data // TODO: we should try to resolve address here LogPrint ("Unexpected address ", value); SetUnreachable (true); - } + } + else + { + // add supported protocol + if (address.host.is_v4 ()) + m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; + else + m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; + } } else if (!strcmp (key, "port")) address.port = boost::lexical_cast(value); + else if (!strcmp (key, "key")) + Base64ToByteStream (value, strlen (value), address.key, 32); } m_Addresses.push_back(address); } @@ -275,22 +289,27 @@ namespace data bool RouterInfo::IsNTCP (bool v4only) const { - for (auto& address : m_Addresses) - { - if (address.transportStyle == eTransportNTCP) - { - if (!v4only || address.host.is_v4 ()) - return true; - } - } - return false; + if (v4only) + return m_SupportedTransports & eNTCPV4; + else + return m_SupportedTransports & (eNTCPV4 | eNTCPV6); + } + + RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) + { + return GetAddress (eTransportNTCP, v4only); } - RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) + RouterInfo::Address * RouterInfo::GetSSUAddress (bool v4only) + { + return GetAddress (eTransportSSU, v4only); + } + + RouterInfo::Address * RouterInfo::GetAddress (TransportStyle s, bool v4only) { for (auto& address : m_Addresses) { - if (address.transportStyle == eTransportNTCP) + if (address.transportStyle == s) { if (!v4only || address.host.is_v4 ()) return &address; diff --git a/RouterInfo.h b/RouterInfo.h index 44dae3f1..d7fc6dd9 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -17,6 +17,14 @@ namespace data { public: + enum SupportedTranports + { + eNTCPV4 = 0x01, + eNTCPV6 = 0x20, + eSSUV4 = 0x40, + eSSUV6 = 0x80 + }; + enum TransportStyle { eTransportUnknown = 0, @@ -31,6 +39,7 @@ namespace data int port; uint64_t date; uint8_t cost; + uint8_t key[32]; // into key for SSU }; RouterInfo (const char * filename); @@ -46,6 +55,7 @@ namespace data uint64_t GetTimestamp () const { return m_Timestamp; }; const std::vector
& GetAddresses () const { return m_Addresses; }; Address * GetNTCPAddress (bool v4only = true); + Address * GetSSUAddress (bool v4only = true); const RoutingKey& GetRoutingKey () const { return m_RoutingKey; }; void AddNTCPAddress (const char * host, int port); @@ -53,6 +63,8 @@ namespace data const char * GetProperty (const char * key) const; bool IsFloodfill () const; bool IsNTCP (bool v4only = true) const; + bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; + void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; @@ -78,6 +90,7 @@ namespace data size_t ReadString (char * str, std::istream& s); void WriteString (const std::string& str, std::ostream& s); void UpdateIdentHashBase64 (); + Address * GetAddress (TransportStyle s, bool v4only); private: @@ -91,6 +104,7 @@ namespace data std::vector
m_Addresses; std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; + uint8_t m_SupportedTransports; }; } } diff --git a/SSU.cpp b/SSU.cpp index aebab995..94dbcfb7 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -1,5 +1,11 @@ +#include #include +#include +#include +#include "CryptoConst.h" #include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" #include "hmac.h" #include "SSU.h" @@ -8,16 +14,244 @@ namespace i2p namespace ssu { - SSUSession::SSUSession (): m_State (eSessionStateUnknown) + SSUSession::SSUSession (SSUServer * server, const boost::asio::ip::udp::endpoint& remoteEndpoint, + i2p::data::RouterInfo * router): m_Server (server), m_RemoteEndpoint (remoteEndpoint), + m_RemoteRouter (router), m_State (eSessionStateUnknown) { } - void SSUSession::ProcessNextMessage (uint8_t * buf, std::size_t len) + void SSUSession::CreateAESKey (uint8_t * pubKey, uint8_t * aesKey) // TODO: move it to base class for NTCP and SSU { + CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); + CryptoPP::SecByteBlock secretKey(dh.AgreedValueLength()); + if (!dh.Agree (secretKey, i2p::context.GetPrivateKey (), pubKey)) + { + LogPrint ("Couldn't create shared key"); + return; + }; + + if (secretKey[0] & 0x80) + { + aesKey[0] = 0; + memcpy (aesKey + 1, secretKey, 31); + } + else + memcpy (aesKey, secretKey, 32); + } + + void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + switch (m_State) + { + case eSessionStateUnknown: + // session request + ProcessSessionRequest (buf, len, senderEndpoint); + break; + case eSessionStateRequestSent: + // session created + ProcessSessionCreated (buf, len); + break; + default: + LogPrint ("SSU state not implemented yet"); + } } + void SSUSession::ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + LogPrint ("Process session request"); + // use our intro key + if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_REQUEST, + i2p::context.GetRouterInfo (), buf, len)) + { + m_State = eSessionStateRequestReceived; + LogPrint ("Session request received"); + m_RemoteEndpoint = senderEndpoint; + SendSessionCreated (buf + sizeof (SSUHeader)); + } + } + + void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) + { + LogPrint ("Process session created"); + if (!m_RemoteRouter) + { + LogPrint ("Unsolicited session created message"); + return; + } + + // use remote intro key + if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_CREATED, *m_RemoteRouter, buf, len)) + { + m_State = eSessionStateCreatedReceived; + LogPrint ("Session created received"); + boost::asio::ip::address_v4 ourAddress (be32toh (*(uint32_t* )(buf + sizeof (SSUHeader) + 257))); + uint16_t ourPort = be16toh (*(uint16_t *)(buf + sizeof (SSUHeader) + 261)); + LogPrint ("Our external address is ", ourAddress.to_string (), ":", ourPort); + } + } + + void SSUSession::SendSessionRequest () + { + auto address = m_RemoteRouter ? m_RemoteRouter->GetSSUAddress () : nullptr; + if (!address) + { + LogPrint ("Missing remote SSU address"); + return; + } + + uint8_t buf[304 + 18]; // 304 bytes for ipv4 (320 for ipv6) + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, i2p::context.GetRouterIdentity ().publicKey, 256); + payload[256] = 4; // we assume ipv4 + *(uint32_t *)(payload + 257) = htobe32 (address->host.to_v4 ().to_ulong ()); + + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, 304, address->key, iv, address->key); + + m_State = eSessionStateRequestSent; + m_Server->Send (buf, 304, m_RemoteEndpoint); + } + + void SSUSession::SendSessionCreated (const uint8_t * x) + { + auto address = m_RemoteRouter ? m_RemoteRouter->GetSSUAddress () : nullptr; + if (!address) + { + LogPrint ("Missing remote SSU address"); + return; + } + uint8_t signedData[532]; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time + memcpy (signedData, x, 256); // x + + uint8_t buf[368 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, i2p::context.GetRouterIdentity ().publicKey, 256); + memcpy (signedData + 256, payload, 256); // y + payload += 256; + *payload = 4; // we assume ipv4 + payload++; + *(uint32_t *)(payload) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); + payload += 4; + *(uint16_t *)(payload) = htobe16 (m_RemoteEndpoint.port ()); + payload += 2; + memcpy (signedData + 512, payload - 6, 6); // remote endpoint IP and port + *(uint32_t *)(signedData + 518) = m_Server->GetEndpoint ().address ().to_v4 ().to_ulong (); // our IP + *(uint16_t *)(signedData + 522) = htobe16 (m_Server->GetEndpoint ().port ()); // our port + *(uint32_t *)(payload) = 0; // relay tag, always 0 for now + payload += 4; + *(uint32_t *)(payload) = htobe32 (i2p::util::GetSecondsSinceEpoch ()); // signed on time + payload += 4; + memcpy (signedData + 524, payload - 8, 8); // relayTag and signed on time + i2p::context.Sign (signedData, 532, payload); // DSA signature + // TODO: fill padding with random data + + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt signature and 8 bytes padding with newly created session key + m_Encryption.SetKeyWithIV (m_SessionKey, 32, iv); + m_Encryption.ProcessData (payload, payload, 48); + + // encrypt message with intro key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, 368, address->key, iv, address->key); + m_State = eSessionStateRequestSent; + m_Server->Send (buf, 368, m_RemoteEndpoint); + } + + bool SSUSession::ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, i2p::data::RouterInfo& r, uint8_t * buf, size_t len) + { + auto address = r.GetSSUAddress (); + if (address) + { + // use intro key for verification and decryption + if (Validate (buf, len, address->key)) + { + Decrypt (buf, len, address->key); + SSUHeader * header = (SSUHeader *)buf; + if ((header->flag >> 4) == expectedPayloadType) + { + CreateAESKey (buf + sizeof (SSUHeader), m_SessionKey); + return true; + } + else + LogPrint ("Unexpected payload type ", (int)(header->flag >> 4)); + } + else + LogPrint ("MAC verifcation failed"); + } + else + LogPrint ("SSU is not supported by ", r.GetIdentHashAbbreviation ()); + return false; + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, uint8_t * aesKey, uint8_t * iv, uint8_t * macKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint ("Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + memcpy (header->iv, iv, 16); + header->flag = payloadType << 4; // MSB is 0 + header->time = htobe32 (i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + m_Encryption.SetKeyWithIV (aesKey, 32, iv); + m_Encryption.ProcessData (encrypted, encrypted, encryptedLen); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, iv, 16); + *(uint16_t *)(buf + len + 16) = htobe16 (encryptedLen); + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); + } + + void SSUSession::Decrypt (uint8_t * buf, size_t len, uint8_t * aesKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint ("Unexpected SSU packet length ", len); + return; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + m_Decryption.SetKeyWithIV (aesKey, 32, header->iv); + encryptedLen = (encryptedLen/16)*16; // make sure 16 bytes boundary + m_Decryption.ProcessData (encrypted, encrypted, encryptedLen); + } + + bool SSUSession::Validate (uint8_t * buf, size_t len, uint8_t * macKey) + { + if (len < sizeof (SSUHeader)) + { + LogPrint ("Unexpected SSU packet length ", len); + return false; + } + SSUHeader * header = (SSUHeader *)buf; + uint8_t * encrypted = &header->flag; + uint16_t encryptedLen = len - (encrypted - buf); + // assume actual buffer size is 18 (16 + 2) bytes more + memcpy (buf + len, header->iv, 16); + *(uint16_t *)(buf + len + 16) = htobe16 (encryptedLen); + uint8_t digest[16]; + i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); + return !memcmp (header->mac, digest, 16); + } + + void SSUSession::Connect () + { + SendSessionRequest (); + } + + void SSUSession::SendI2NPMessage (I2NPMessage * msg) + { + // TODO: + } + SSUServer::SSUServer (boost::asio::io_service& service, int port): - m_Socket (service, boost::asio::ip::udp::v4 (), port) + m_Endpoint (boost::asio::ip::udp::v4 (), port), m_Socket (service, m_Endpoint) { } @@ -37,6 +271,11 @@ namespace ssu m_Socket.close (); } + void SSUServer::Send (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) + { + m_Socket.send_to (boost::asio::buffer (buf, len), to); + } + void SSUServer::Receive () { m_Socket.async_receive_from (boost::asio::buffer (m_ReceiveBuffer, SSU_MTU), m_SenderEndpoint, @@ -52,18 +291,46 @@ namespace ssu auto it = m_Sessions.find (m_SenderEndpoint); if (it != m_Sessions.end ()) session = it->second; - if (session) + if (!session) { - session = new SSUSession (); + session = new SSUSession (this, m_SenderEndpoint); m_Sessions[m_SenderEndpoint] = session; LogPrint ("New SSU session from ", m_SenderEndpoint.address ().to_string (), ":", m_SenderEndpoint.port (), " created"); } - session->ProcessNextMessage (m_ReceiveBuffer, bytes_transferred); + session->ProcessNextMessage (m_ReceiveBuffer, bytes_transferred, m_SenderEndpoint); Receive (); } else LogPrint ("SSU receive error: ", ecode.message ()); } + + SSUSession * SSUServer::GetSession (i2p::data::RouterInfo * router) + { + SSUSession * session = nullptr; + if (router) + { + auto address = router->GetSSUAddress (); + if (address) + { + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto it = m_Sessions.find (remoteEndpoint); + if (it != m_Sessions.end ()) + session = it->second; + else + { + // otherwise create new session + session = new SSUSession (this, remoteEndpoint, router); + m_Sessions[remoteEndpoint] = session; + LogPrint ("New SSU session to [", router->GetIdentHashAbbreviation (), "] ", + remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port (), " created"); + session->Connect (); + } + } + else + LogPrint ("Router ", router->GetIdentHashAbbreviation (), " doesn't have SSU address"); + } + return session; + } } } diff --git a/SSU.h b/SSU.h index 8bb2c6fa..1dcf536e 100644 --- a/SSU.h +++ b/SSU.h @@ -4,17 +4,33 @@ #include #include #include +#include +#include +#include "I2PEndian.h" +#include "RouterInfo.h" +#include "I2NPProtocol.h" namespace i2p { namespace ssu { +#pragma pack(1) + struct SSUHeader + { + uint8_t mac[16]; + uint8_t iv[16]; + uint8_t flag; + uint32_t time; + }; +#pragma pack() + const int SSU_MTU = 1484; // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; + const uint8_t PAYLOAD_TYPE_SESSION_DESTROY = 8; const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; @@ -33,16 +49,41 @@ namespace ssu eSessionStateEstablised }; + class SSUServer; class SSUSession { public: - SSUSession (); - void ProcessNextMessage (uint8_t * buf, std::size_t len); + SSUSession (SSUServer * server, const boost::asio::ip::udp::endpoint& remoteEndpoint, + i2p::data::RouterInfo * router = nullptr); + void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + + void Connect (); + void SendI2NPMessage (I2NPMessage * msg); + + private: + + void CreateAESKey (uint8_t * pubKey, uint8_t * aesKey); // TODO: shouldn't be here + + void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void SendSessionRequest (); + void ProcessSessionCreated (uint8_t * buf, size_t len); + void SendSessionCreated (const uint8_t * x); + + bool ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, i2p::data::RouterInfo& r, uint8_t * buf, size_t len); + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, uint8_t * aesKey, uint8_t * iv, uint8_t * macKey); + void Decrypt (uint8_t * buf, size_t len, uint8_t * aesKey); + bool Validate (uint8_t * buf, size_t len, uint8_t * macKey); private: + SSUServer * m_Server; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + i2p::data::RouterInfo * m_RemoteRouter; SessionState m_State; + CryptoPP::CBC_Mode::Encryption m_Encryption; + CryptoPP::CBC_Mode::Decryption m_Decryption; + uint8_t m_SessionKey[32]; }; class SSUServer @@ -53,6 +94,10 @@ namespace ssu ~SSUServer (); void Start (); void Stop (); + SSUSession * GetSession (i2p::data::RouterInfo * router); + + const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; + void Send (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); private: @@ -61,9 +106,10 @@ namespace ssu private: + boost::asio::ip::udp::endpoint m_Endpoint; boost::asio::ip::udp::socket m_Socket; boost::asio::ip::udp::endpoint m_SenderEndpoint; - uint8_t m_ReceiveBuffer[SSU_MTU]; + uint8_t m_ReceiveBuffer[2*SSU_MTU]; std::map m_Sessions; }; } diff --git a/Streaming.cpp b/Streaming.cpp index 99d51618..f73c5125 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1,4 +1,3 @@ -#include "I2PEndian.h" #include #include #include @@ -26,60 +25,35 @@ namespace stream { while (auto packet = m_ReceiveQueue.Get ()) delete packet; + for (auto it: m_SavedPackets) + delete it; } void Stream::HandleNextPacket (Packet * packet) { - const uint8_t * buf = packet->buf; - buf += 4; // sendStreamID if (!m_SendStreamID) - m_SendStreamID = be32toh (*(uint32_t *)buf); - buf += 4; // receiveStreamID - uint32_t receivedSeqn = be32toh (*(uint32_t *)buf); - buf += 4; // sequenceNum - buf += 4; // ackThrough - int nackCount = buf[0]; - buf++; // NACK count - buf += 4*nackCount; // NACKs - buf++; // resendDelay - uint16_t flags = be16toh (*(uint16_t *)buf); - buf += 2; // flags - uint16_t optionalSize = be16toh (*(uint16_t *)buf); - buf += 2; // optional size - const uint8_t * optionalData = buf; - buf += optionalSize; - - // process flags - if (flags & PACKET_FLAG_SYNCHRONIZE) - { - LogPrint ("Synchronize"); - } + m_SendStreamID = packet->GetReceiveStreamID (); - if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) - { - LogPrint ("Signature"); - optionalData += 40; - } - - if (flags & PACKET_FLAG_FROM_INCLUDED) - { - LogPrint ("From identity"); - optionalData += sizeof (i2p::data::Identity); - } - - // we have reached payload section - LogPrint ("seqn=", receivedSeqn, ", flags=", flags); + uint32_t receivedSeqn = packet->GetSeqn (); + LogPrint ("Received seqn=", receivedSeqn); if (!receivedSeqn || receivedSeqn == m_LastReceivedSequenceNumber + 1) { - // we have received next message - packet->offset = buf - packet->buf; - if (packet->GetLength () > 0) - m_ReceiveQueue.Put (packet); - else - delete packet; - - m_LastReceivedSequenceNumber = receivedSeqn; - SendQuickAck (); + // we have received next in sequence message + ProcessPacket (packet); + + // we should also try stored messages if any + for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();) + { + if ((*it)->GetSeqn () == m_LastReceivedSequenceNumber + 1) + { + Packet * savedPacket = *it; + m_SavedPackets.erase (it++); + + ProcessPacket (savedPacket); + } + else + break; + } } else { @@ -90,15 +64,56 @@ namespace stream m_OutboundTunnel = i2p::tunnel::tunnels.GetNextOutboundTunnel (); // pick another tunnel if (m_OutboundTunnel) SendQuickAck (); // resend ack for previous message again + delete packet; // packet dropped } else { LogPrint ("Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); - // actually do nothing. just wait for missing message again + // save message and wait for missing message again + SavePacket (packet); } - delete packet; // packet dropped } - + } + + void Stream::SavePacket (Packet * packet) + { + m_SavedPackets.insert (packet); + } + + void Stream::ProcessPacket (Packet * packet) + { + // process flags + uint32_t receivedSeqn = packet->GetSeqn (); + uint16_t flags = packet->GetFlags (); + LogPrint ("Process seqn=", receivedSeqn, ", flags=", flags); + + const uint8_t * optionData = packet->GetOptionData (); + if (flags & PACKET_FLAG_SYNCHRONIZE) + { + LogPrint ("Synchronize"); + } + + if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) + { + LogPrint ("Signature"); + optionData += 40; + } + + if (flags & PACKET_FLAG_FROM_INCLUDED) + { + LogPrint ("From identity"); + optionData += sizeof (i2p::data::Identity); + } + + packet->offset = packet->GetPayload () - packet->buf; + if (packet->GetLength () > 0) + m_ReceiveQueue.Put (packet); + else + delete packet; + + m_LastReceivedSequenceNumber = receivedSeqn; + SendQuickAck (); + if (flags & PACKET_FLAG_CLOSE) { LogPrint ("Closed"); @@ -106,7 +121,7 @@ namespace stream m_ReceiveQueue.WakeUp (); } } - + size_t Stream::Send (uint8_t * buf, size_t len, int timeout) { if (!m_IsOpen) diff --git a/Streaming.h b/Streaming.h index cc772190..18d09b5d 100644 --- a/Streaming.h +++ b/Streaming.h @@ -3,7 +3,9 @@ #include #include +#include #include +#include "I2PEndian.h" #include "Queue.h" #include "Identity.h" #include "LeaseSet.h" @@ -37,6 +39,25 @@ namespace stream Packet (): len (0), offset (0) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len - offset; }; + + uint32_t GetSendStreamID () const { return be32toh (*(uint32_t *)buf); }; + uint32_t GetReceiveStreamID () const { return be32toh (*(uint32_t *)(buf + 4)); }; + uint32_t GetSeqn () const { return be32toh (*(uint32_t *)(buf + 8)); }; + uint32_t GetAckThrough () const { return be32toh (*(uint32_t *)(buf + 12)); }; + uint8_t GetNACKCount () const { return buf[16]; }; + const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags + uint16_t GetFlags () const { return be16toh (*(uint16_t *)(GetOption () - 2)); }; + uint16_t GetOptionSize () const { return be16toh (*(uint16_t *)GetOption ()); }; + const uint8_t * GetOptionData () const { return GetOption () + 2; }; + const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); }; + }; + + struct PacketCmp + { + bool operator() (const Packet * p1, const Packet * p2) const + { + return p1->GetSeqn () < p2->GetSeqn (); + }; }; class StreamingDestination; @@ -61,6 +82,9 @@ namespace stream void ConnectAndSend (uint8_t * buf, size_t len); void SendQuickAck (); + + void SavePacket (Packet * packet); + void ProcessPacket (Packet * packet); private: @@ -69,6 +93,7 @@ namespace stream StreamingDestination * m_LocalDestination; const i2p::data::LeaseSet * m_RemoteLeaseSet; i2p::util::Queue m_ReceiveQueue; + std::set m_SavedPackets; i2p::tunnel::OutboundTunnel * m_OutboundTunnel; }; diff --git a/Tunnel.cpp b/Tunnel.cpp index fe904369..1163f47e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -397,11 +397,12 @@ namespace tunnel { LogPrint ("Creating two hops outbound tunnel..."); + auto firstHop = i2p::data::netdb.GetRandomNTCPRouter (); // first hop must be NTCP CreateTunnel ( new TunnelConfig (std::vector { - i2p::data::netdb.GetRandomNTCPRouter (), // first hop must be NTCP - i2p::data::netdb.GetRandomRouter () + firstHop, + i2p::data::netdb.GetRandomRouter (firstHop) }, inboundTunnel->GetTunnelConfig ())); } @@ -449,8 +450,8 @@ namespace tunnel CreateTunnel ( new TunnelConfig (std::vector { - i2p::data::netdb.GetRandomNTCPRouter (), - router != &i2p::context.GetRouterInfo () ? router : i2p::data::netdb.GetRandomNTCPRouter () + i2p::data::netdb.GetRandomRouter (outboundTunnel->GetEndpointRouter ()), + router != &i2p::context.GetRouterInfo () ? router : i2p::data::netdb.GetRandomNTCPRouter () // last hop must be NTCP }), outboundTunnel); } diff --git a/Tunnel.h b/Tunnel.h index a189da0d..3498eae3 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -67,7 +67,8 @@ namespace tunnel void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg); void SendTunnelDataMsg (std::vector msgs); // multiple messages - + const i2p::data::RouterInfo * GetEndpointRouter () const + { return GetTunnelConfig ()->GetLastHop ()->router; }; size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase diff --git a/base64.cpp b/base64.cpp index b75214c9..e731bedc 100644 --- a/base64.cpp +++ b/base64.cpp @@ -27,6 +27,11 @@ namespace data '4', '5', '6', '7', '8', '9', '-', '~' }; + const char * GetBase64SubstitutionTable () + { + return T64; + } + /* * Reverse Substitution Table (built in run time) */ diff --git a/base64.h b/base64.h index d67927e9..47a65def 100644 --- a/base64.h +++ b/base64.h @@ -11,9 +11,9 @@ namespace data size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); - + const char * GetBase64SubstitutionTable (); + size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); - } } diff --git a/hmac.h b/hmac.h index 7b77af28..6e9c042b 100644 --- a/hmac.h +++ b/hmac.h @@ -18,8 +18,7 @@ namespace crypto // digest is 16 bytes // block size is 64 bytes { - size_t totalLen = len + 64 + 32; - uint8_t * buf = new uint8_t[totalLen]; // TODO: reuse buffers + uint8_t buf[2048]; // ikeypad ((uint64_t *)buf)[0] = ((uint64_t *)key)[0] ^ IPAD; ((uint64_t *)buf)[1] = ((uint64_t *)key)[1] ^ IPAD; @@ -47,11 +46,10 @@ namespace crypto // copy first hash after okeypad memcpy (buf + 64, hash, 16); // fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (buf + 72, 0, 16); + memset (buf + 80, 0, 16); // calculate digest - CryptoPP::Weak1::MD5().CalculateDigest (digest, buf, totalLen); - delete[] buf; + CryptoPP::Weak1::MD5().CalculateDigest (digest, buf, 96); } } } diff --git a/i2p.cpp b/i2p.cpp index 51c6d1eb..aed38cd0 100644 --- a/i2p.cpp +++ b/i2p.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "Log.h" #include "base64.h" #include "Transports.h" @@ -10,10 +11,11 @@ #include "Tunnel.h" #include "NetDb.h" #include "HTTPServer.h" +#include "util.h" -int main( int, char** ) +int main( int argc, char* argv[] ) { - + i2p::util::config::OptionParser(argc,argv); #ifdef _WIN32 setlocale(LC_CTYPE, ""); SetConsoleCP(1251); @@ -21,7 +23,17 @@ int main( int, char** ) setlocale(LC_ALL, "Russian"); #endif - i2p::util::HTTPServer httpServer (7070); + LogPrint("\n\n\n\ni2pd starting\n"); + LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string()); + i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); + + //TODO: This is an ugly workaround. fix it. + //TODO: Autodetect public IP. + i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"), + i2p::util::config::GetArg("-port", 17070)); + int httpport = i2p::util::config::GetArg("-httpport", 7070); + + i2p::util::HTTPServer httpServer (httpport); httpServer.Start (); i2p::data::netdb.Start (); diff --git a/util.cpp b/util.cpp new file mode 100644 index 00000000..88a1f7c8 --- /dev/null +++ b/util.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "Log.h" + +namespace i2p +{ +namespace util +{ + +namespace config +{ + std::map mapArgs; + std::map > mapMultiArgs; + + void OptionParser(int argc, const char* const argv[]) + { + mapArgs.clear(); + mapMultiArgs.clear(); + for (int i = 1; i < argc; i++) + { + std::string strKey (argv[i]); + std::string strValue; + size_t has_data = strKey.find('='); + if (has_data != std::string::npos) + { + strValue = strKey.substr(has_data+1); + strKey = strKey.substr(0, has_data); + } + +#ifdef WIN32 + boost::to_lower(strKey); + if (boost::algorithm::starts_with(strKey, "/")) + strKey = "-" + strKey.substr(1); +#endif + if (strKey[0] != '-') + break; + + mapArgs[strKey] = strValue; + mapMultiArgs[strKey].push_back(strValue); + } + + BOOST_FOREACH(PAIRTYPE(const std::string,std::string)& entry, mapArgs) + { + std::string name = entry.first; + + // interpret --foo as -foo (as long as both are not set) + if (name.find("--") == 0) + { + std::string singleDash(name.begin()+1, name.end()); + if (mapArgs.count(singleDash) == 0) + mapArgs[singleDash] = entry.second; + name = singleDash; + } + } + } + + const char* GetCharArg(const std::string& strArg, const std::string& nDefault) + { + if (mapArgs.count(strArg)) + return mapArgs[strArg].c_str(); + return nDefault.c_str(); + } + + std::string GetArg(const std::string& strArg, const std::string& strDefault) + { + if (mapArgs.count(strArg)) + return mapArgs[strArg]; + return strDefault; + } + + int GetArg(const std::string& strArg, int nDefault) + { + if (mapArgs.count(strArg)) + return atoi(mapArgs[strArg].c_str()); + return nDefault; + } +} + +namespace filesystem +{ + const boost::filesystem::path &GetDataDir() + { + static boost::filesystem::path path; + + if (i2p::util::config::mapArgs.count("-datadir")) { + path = boost::filesystem::system_complete(i2p::util::config::mapArgs["-datadir"]); + } else { + path = GetDefaultDataDir(); + } + + if (!boost::filesystem::exists( path )) + { + // Create data directory + if (!boost::filesystem::create_directory( path )) + { + LogPrint("Failed to create data directory!"); + path = ""; + return path; + } + } + if (!boost::filesystem::is_directory(path)) { + path = GetDefaultDataDir(); + } + return path; + } + + boost::filesystem::path GetConfigFile() + { + boost::filesystem::path pathConfigFile(i2p::util::config::GetArg("-conf", "i2p.conf")); + if (!pathConfigFile.is_complete()) pathConfigFile = GetDataDir() / pathConfigFile; + return pathConfigFile; + } + + void ReadConfigFile(std::map& mapSettingsRet, + std::map >& mapMultiSettingsRet) + { + boost::filesystem::ifstream streamConfig(GetConfigFile()); + if (!streamConfig.good()) + return; // No i2pd.conf file is OK + + std::set setOptions; + setOptions.insert("*"); + + for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) + { + // Don't overwrite existing settings so command line settings override i2pd.conf + std::string strKey = std::string("-") + it->string_key; + if (mapSettingsRet.count(strKey) == 0) + { + mapSettingsRet[strKey] = it->value[0]; + } + mapMultiSettingsRet[strKey].push_back(it->value[0]); + } + } + + boost::filesystem::path GetDefaultDataDir() + { + // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd + // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd + // Mac: ~/Library/Application Support/i2pd + // Unix: ~/.i2pd +#ifdef WIN32 + // Windows + return GetSpecialFolderPath(CSIDL_APPDATA) / "i2pd"; +#else + boost::filesystem::path pathRet; + char* pszHome = getenv("HOME"); + if (pszHome == NULL || strlen(pszHome) == 0) + pathRet = boost::filesystem::path("/"); + else + pathRet = boost::filesystem::path(pszHome); +#ifdef MAC_OSX + // Mac + pathRet /= "Library/Application Support"; + boost::filesystem::create_directory(pathRet); + return pathRet / "i2pd"; +#else + // Unix + return pathRet / ".i2pd"; +#endif +#endif + } +} + +namespace http +{ + std::string httpRequest(const std::string& address) + { + try + { + i2p::util::http::url u(address); + boost::asio::ip::tcp::iostream site; + // please don't uncomment following line because it's not compatible with boost 1.46 + // 1.46 is default boost for Ubuntu 12.04 LTS + //site.expires_from_now (boost::posix_time::seconds(30)); + site.connect(u.host_, "http"); + if (site) + { + // User-Agent is needed to get the server list routerInfo files. + site << "GET " << u.path_ << " HTTP/1.0\r\nHost: " << u.host_ + << "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n"; + // read response + std::string version, statusMessage; + site >> version; // HTTP version + int status; + site >> status; // status + std::getline (site, statusMessage); + if (status == 200) // OK + { + std::string header; + while (std::getline(site, header) && header != "\r"){} + std::stringstream ss; + ss << site.rdbuf(); + return ss.str(); + } + else + { + LogPrint ("HTTP response ", status); + return ""; + } + } + else + { + LogPrint ("Can't connect to ", address); + return ""; + } + } + catch (std::exception& ex) + { + LogPrint ("Failed to download ", address, " : ", ex.what ()); + return ""; + } + } + + url::url(const std::string& url_s) + { + parse(url_s); + } + + void url::parse(const std::string& url_s) + { + const std::string prot_end("://"); + std::string::const_iterator prot_i = search(url_s.begin(), url_s.end(), + prot_end.begin(), prot_end.end()); + protocol_.reserve(distance(url_s.begin(), prot_i)); + transform(url_s.begin(), prot_i, + back_inserter(protocol_), + std::ptr_fun(tolower)); // protocol is icase + if( prot_i == url_s.end() ) + return; + advance(prot_i, prot_end.length()); + std::string::const_iterator path_i = find(prot_i, url_s.end(), '/'); + host_.reserve(distance(prot_i, path_i)); + transform(prot_i, path_i, + back_inserter(host_), + std::ptr_fun(tolower)); // host is icase + std::string::const_iterator query_i = find(path_i, url_s.end(), '?'); + path_.assign(path_i, query_i); + if( query_i != url_s.end() ) + ++query_i; + query_.assign(query_i, url_s.end()); + } + +} + + + + +} // Namespace end +} diff --git a/util.h b/util.h new file mode 100644 index 00000000..519e51b3 --- /dev/null +++ b/util.h @@ -0,0 +1,49 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include + +#define PAIRTYPE(t1, t2) std::pair + +namespace i2p +{ +namespace util +{ + namespace config + { + extern std::map mapArgs; + extern std::map > mapMultiArgs; + void OptionParser(int argc, const char* const argv[]); + int GetArg(const std::string& strArg, int nDefault); + std::string GetArg(const std::string& strArg, const std::string& strDefault); + const char* GetCharArg(const std::string& strArg, const std::string& nDefault); + } + + namespace filesystem + { + const boost::filesystem::path &GetDataDir(); + boost::filesystem::path GetDefaultDataDir(); + boost::filesystem::path GetConfigFile(); + void ReadConfigFile(std::map& mapSettingsRet, + std::map >& mapMultiSettingsRet); + } + + namespace http + { + std::string httpRequest(const std::string& address); + struct url { + url(const std::string& url_s); // omitted copy, ==, accessors, ... + private: + void parse(const std::string& url_s); + public: + std::string protocol_, host_, path_, query_; + }; + } +} +} + + +#endif