diff --git a/.gitignore b/.gitignore index b9d6bd92..2cb9ea10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +# i2pd +*.o +router.info +router.keys +i2p +netDb + ################# ## Eclipse ################# diff --git a/Garlic.cpp b/Garlic.cpp index ad824fd7..cdb8860e 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -236,8 +236,8 @@ namespace garlic auto it = m_Sessions.find (destination->GetIdentHash ()); if (it != m_Sessions.end ()) { - m_Sessions.erase (it); delete it->second; + m_Sessions.erase (it); } GarlicRoutingSession * session = new GarlicRoutingSession (destination, 0); // not follow-on messages expected m_Sessions[destination->GetIdentHash ()] = session; 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/I2PEndian.cpp b/I2PEndian.cpp index 1fccf47f..fa4e08cb 100644 --- a/I2PEndian.cpp +++ b/I2PEndian.cpp @@ -1,81 +1,81 @@ -#include "I2PEndian.h" - -// http://habrahabr.ru/post/121811/ -// http://codepad.org/2ycmkz2y - -#include "LittleBigEndian.h" - -uint16_t htobe16(uint16_t int16) -{ - BigEndian u16(int16); - return u16.raw_value; -} - -uint32_t htobe32(uint32_t int32) -{ - BigEndian u32(int32); - return u32.raw_value; -} - -uint64_t htobe64(uint64_t int64) -{ - BigEndian u64(int64); - return u64.raw_value; -} - -uint16_t be16toh(uint16_t big16) -{ - LittleEndian u16(big16); - return u16.raw_value; -} - -uint32_t be32toh(uint32_t big32) -{ - LittleEndian u32(big32); - return u32.raw_value; -} - -uint64_t be64toh(uint64_t big64) -{ - LittleEndian u64(big64); - return u64.raw_value; -} - -/* it can be used in Windows 8 -#include - -uint16_t htobe16(uint16_t int16) -{ - return htons(int16); -} - -uint32_t htobe32(uint32_t int32) -{ - return htonl(int32); -} - -uint64_t htobe64(uint64_t int64) -{ - // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx - //return htonll(int64); - return 0; -} - - -uint16_t be16toh(uint16_t big16) -{ - return ntohs(big16); -} - -uint32_t be32toh(uint32_t big32) -{ - return ntohl(big32); -} - -uint64_t be64toh(uint64_t big64) -{ - // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx - //return ntohll(big64); - return 0; -} -*/ \ No newline at end of file +//#include "I2PEndian.h" +// +//// http://habrahabr.ru/post/121811/ +//// http://codepad.org/2ycmkz2y +// +//#include "LittleBigEndian.h" +// +//uint16_t htobe16(uint16_t int16) +//{ +// BigEndian u16(int16); +// return u16.raw_value; +//} +// +//uint32_t htobe32(uint32_t int32) +//{ +// BigEndian u32(int32); +// return u32.raw_value; +//} +// +//uint64_t htobe64(uint64_t int64) +//{ +// BigEndian u64(int64); +// return u64.raw_value; +//} +// +//uint16_t be16toh(uint16_t big16) +//{ +// LittleEndian u16(big16); +// return u16.raw_value; +//} +// +//uint32_t be32toh(uint32_t big32) +//{ +// LittleEndian u32(big32); +// return u32.raw_value; +//} +// +//uint64_t be64toh(uint64_t big64) +//{ +// LittleEndian u64(big64); +// return u64.raw_value; +//} +// +///* it can be used in Windows 8 +//#include +// +//uint16_t htobe16(uint16_t int16) +//{ +// return htons(int16); +//} +// +//uint32_t htobe32(uint32_t int32) +//{ +// return htonl(int32); +//} +// +//uint64_t htobe64(uint64_t int64) +//{ +// // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx +// //return htonll(int64); +// return 0; +//} +// +// +//uint16_t be16toh(uint16_t big16) +//{ +// return ntohs(big16); +//} +// +//uint32_t be32toh(uint32_t big32) +//{ +// return ntohl(big32); +//} +// +//uint64_t be64toh(uint64_t big64) +//{ +// // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx +// //return ntohll(big64); +// return 0; +//} +//*/ \ No newline at end of file diff --git a/I2PEndian.h b/I2PEndian.h index 01ba73e8..3014f564 100644 --- a/I2PEndian.h +++ b/I2PEndian.h @@ -5,14 +5,16 @@ #include #else #include +// +//uint16_t htobe16(uint16_t int16); +//uint32_t htobe32(uint32_t int32); +//uint64_t htobe64(uint64_t int64); +// +//uint16_t be16toh(uint16_t big16); +//uint32_t be32toh(uint32_t big32); +//uint64_t be64toh(uint64_t big64); -uint16_t htobe16(uint16_t int16); -uint32_t htobe32(uint32_t int32); -uint64_t htobe64(uint64_t int64); - -uint16_t be16toh(uint16_t big16); -uint32_t be32toh(uint32_t big32); -uint64_t be64toh(uint64_t big64); +#include "portable_endian.h" #endif 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 1fec1950..abe4238d 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ 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 + I2NPProtocol.o Log.o Garlic.o HTTPServer.o Streaming.o Identity.o SSU.o util.o INCFLAGS = -LDFLAGS = -Wl,-rpath,/usr/local/lib -lcryptopp -lboost_system -lboost_filesystem +LDFLAGS = -Wl,-rpath,/usr/local/lib -lcryptopp -lboost_system -lboost_filesystem -lpthread LIBS = all: i2p diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 61e7944f..3e3d551d 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -517,10 +517,10 @@ namespace ntcp } - NTCPClient::NTCPClient (boost::asio::io_service& service, const char * address, + NTCPClient::NTCPClient (boost::asio::io_service& service, const boost::asio::ip::address& address, int port, i2p::data::RouterInfo& in_RouterInfo): NTCPSession (service, in_RouterInfo), - m_Endpoint (boost::asio::ip::address::from_string (address), port) + m_Endpoint (address, port) { Connect (); } diff --git a/NTCPSession.h b/NTCPSession.h index 5d33ef10..71cf5af9 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -144,7 +144,7 @@ namespace ntcp { public: - NTCPClient (boost::asio::io_service& service, const char * address, int port, i2p::data::RouterInfo& in_RouterInfo); + NTCPClient (boost::asio::io_service& service, const boost::asio::ip::address& address, int port, i2p::data::RouterInfo& in_RouterInfo); private: diff --git a/NetDb.cpp b/NetDb.cpp index 9b454598..278e8286 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -1,6 +1,7 @@ #include "I2PEndian.h" #include #include +#include #include #include #include "base64.h" @@ -8,6 +9,7 @@ #include "Timestamp.h" #include "I2NPProtocol.h" #include "Tunnel.h" +#include "Transports.h" #include "RouterContext.h" #include "Garlic.h" #include "NetDb.h" @@ -29,6 +31,16 @@ 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; + } NetDb netdb; @@ -165,63 +177,106 @@ 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(const char * directory) + { + boost::filesystem::path p (directory); + LogPrint (directory, " doesn't exist, trying to create it."); + if (!boost::filesystem::create_directory (p)) + { + LogPrint("Failed to create directory ", directory); + 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 (p / suffix) )) return false; + } + return true; + } void NetDb::Load (const char * directory) { boost::filesystem::path p (directory); - if (boost::filesystem::exists (p)) + if (!boost::filesystem::exists (p)) { - int numRouters = 0; - boost::filesystem::directory_iterator end; - for (boost::filesystem::directory_iterator it (p); it != end; ++it) + if (!CreateNetDb(directory)) return; + } + // TODO: Reseed if needed. + 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"; }; int count = 0, deletedCount = 0; + auto total = m_RouterInfos.size (); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_RouterInfos) { if (it.second->IsUpdated ()) { - std::ofstream r (GetFilePath(directory, it.second)); + std::ofstream r (GetFilePath(directory, it.second), std::ofstream::binary); r.write ((char *)it.second->GetBuffer (), it.second->GetBufferLen ()); it.second->SetUpdated (false); count++; } - else if (it.second->IsUnreachable ()) + else { - if (boost::filesystem::exists (GetFilePath (directory, it.second))) - { - boost::filesystem::remove (GetFilePath (directory, it.second)); - deletedCount++; + // RouterInfo expires in 72 hours if more than 300 + if (total > 300 && ts > it.second->GetTimestamp () + 3*24*3600*1000LL) // 3 days + { + total--; + it.second->SetUnreachable (true); } + + if (it.second->IsUnreachable ()) + { + if (boost::filesystem::exists (GetFilePath (directory, it.second))) + { + boost::filesystem::remove (GetFilePath (directory, it.second)); + deletedCount++; + } + } } } if (count > 0) @@ -239,32 +294,49 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, bool isLeaseSet) { - auto floodfill= GetRandomNTCPRouter (true); - if (floodfill) - RequestDestination (destination, floodfill, isLeaseSet); - else - LogPrint ("No floodfill routers found"); - } - - void NetDb::RequestDestination (const IdentHash& destination, const RouterInfo * floodfill, bool isLeaseSet) - { - if (!floodfill) return; - 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); - dest->SetLastOutboundTunnel (outbound); - auto msg = dest->CreateRequestMessage (floodfill, inbound); - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); - } + 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); + } + else + LogPrint ("No more floodfills found"); + } + else + 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) @@ -377,18 +449,25 @@ 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 { // no more requests for detination possible. delete it - m_RequestedDestinations.erase (it); delete it->second; + m_RequestedDestinations.erase (it); } } else @@ -402,7 +481,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 ..."); @@ -449,8 +528,8 @@ namespace data auto it = m_RequestedDestinations.find (dest); if (it != m_RequestedDestinations.end ()) { - m_RequestedDestinations.erase (it); delete it->second; + m_RequestedDestinations.erase (it); } } @@ -470,16 +549,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; - for (auto it: m_RouterInfos) + uint32_t ind = rnd.GenerateWord32 (0, m_RouterInfos.size () - 1); + for (int j = 0; j < 2; j++) { - if (i >= ind) return it.second; - 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 nullptr; + return nullptr; // seem we have too few routers } void NetDb::PostI2NPMsg (I2NPMessage * msg) @@ -487,7 +579,8 @@ namespace data if (msg) m_Queue.Put (msg); } - const RouterInfo * NetDb::GetClosestFloodfill (const IdentHash& destination) const + const RouterInfo * NetDb::GetClosestFloodfill (const IdentHash& destination, + const std::set& excluded) const { RouterInfo * r = nullptr; XORMetric minMetric; @@ -495,7 +588,7 @@ namespace data minMetric.SetMax (); for (auto it: m_RouterInfos) { - if (it.second->IsFloodfill () &&! it.second->IsUnreachable ()) + if (it.second->IsFloodfill () &&! it.second->IsUnreachable () && !excluded.count (it.first)) { XORMetric m = destKey ^ it.second->GetRoutingKey (); if (m < minMetric) @@ -507,5 +600,46 @@ namespace data } return r; } + + //TODO: Move to reseed. + //TODO: Implement v1 & v2 reseeding. Lightweight zip library is needed for v2. + // orignal: zip is part of crypto++, see implementation of DatabaseStoreMsg + //TODO: Implement SU3, utils. + 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 02a585ef..1397e9e4 100644 --- a/NetDb.h +++ b/NetDb.h @@ -26,12 +26,15 @@ namespace data const IdentHash& GetDestination () const { return m_Destination; }; int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; - const RouterInfo * GetLastRouter () const { return m_LastRouter; }; + const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; + 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; }; @@ -62,23 +65,24 @@ namespace data void RequestDestination (const char * b32); // in base32 void RequestDestination (const IdentHash& destination, bool isLeaseSet = false); - void RequestDestination (const IdentHash& destination, const RouterInfo * floodfill, bool isLeaseSet = false); - + void HandleDatabaseStoreMsg (uint8_t * buf, size_t len); 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(const char * 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; + const RouterInfo * GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; RequestedDestination * CreateRequestedDestination (const IdentHash& dest, bool isLeaseSet, bool isExploratory = false); diff --git a/RouterContext.cpp b/RouterContext.cpp index 718a9f36..68f6508a 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -3,6 +3,7 @@ #include #include "CryptoConst.h" #include "RouterContext.h" +#include "util.h" namespace i2p { @@ -45,7 +46,7 @@ namespace i2p auto address = m_RouterInfo.GetNTCPAddress (); if (address) { - address->host = host; + address->host = boost::asio::ip::address::from_string (host); address->port = port; } @@ -60,7 +61,7 @@ namespace i2p bool RouterContext::Load () { - std::ifstream fk (ROUTER_KEYS); + std::ifstream fk (ROUTER_KEYS, std::ifstream::binary | std::ofstream::in); if (!fk.is_open ()) return false; fk.read ((char *)&m_Keys, sizeof (m_Keys)); @@ -74,10 +75,10 @@ namespace i2p void RouterContext::Save () { - std::ofstream fk (ROUTER_KEYS); + std::ofstream fk (ROUTER_KEYS, std::ofstream::binary | std::ofstream::out); fk.write ((char *)&m_Keys, sizeof (m_Keys)); - std::ofstream fi (ROUTER_INFO); + std::ofstream fi (ROUTER_INFO, std::ofstream::binary | std::ofstream::out); fi.write ((char *)m_RouterInfo.GetBuffer (), m_RouterInfo.GetBufferLen ()); } -} \ No newline at end of file +} diff --git a/RouterInfo.cpp b/RouterInfo.cpp index d804ecad..44bd90e4 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -12,18 +12,19 @@ #include "RouterInfo.h" #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; @@ -41,7 +42,7 @@ namespace data void RouterInfo::ReadFromFile (const char * filename) { - std::ifstream s(filename); + std::ifstream s(filename, std::ifstream::binary); if (s.is_open ()) { s.seekg (0,std::ios::end); @@ -95,15 +96,34 @@ namespace data size = be16toh (size); while (r < size) { - char key[50], value[50]; + char key[500], value[500]; r += ReadString (key, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!strcmp (key, "host")) - address.host = value; + { + boost::system::error_code ecode; + address.host = boost::asio::ip::address::from_string (value, ecode); + if (ecode) + { + // 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); } @@ -117,7 +137,13 @@ namespace data size = be16toh (size); while (r < size) { +#ifdef _WIN32 + char key[500], value[500]; + // TODO: investigate why properties get read as one long string under Windows + // length should not be more than 44 +#else char key[50], value[50]; +#endif r += ReadString (key, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, s); @@ -166,7 +192,7 @@ namespace data std::stringstream properties; WriteString ("host", properties); properties << '='; - WriteString (address.host, properties); + WriteString (address.host.to_string (), properties); properties << ';'; WriteString ("port", properties); properties << '='; @@ -227,7 +253,7 @@ namespace data void RouterInfo::AddNTCPAddress (const char * host, int port) { Address addr; - addr.host = host; + addr.host = boost::asio::ip::address::from_string (host); addr.port = port; addr.transportStyle = eTransportNTCP; addr.cost = 2; @@ -256,22 +282,33 @@ namespace data return false; } - bool RouterInfo::IsNTCP () const + bool RouterInfo::IsNTCP (bool v4only) const { - for (auto& address : m_Addresses) - { - if (address.transportStyle == eTransportNTCP) - 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 () + 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) - return &address; + if (address.transportStyle == s) + { + if (!v4only || address.host.is_v4 ()) + return &address; + } } return nullptr; } diff --git a/RouterInfo.h b/RouterInfo.h index a8d59adf..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, @@ -27,10 +35,11 @@ namespace data struct Address { TransportStyle transportStyle; - std::string host; + boost::asio::ip::address host; int port; uint64_t date; uint8_t cost; + uint8_t key[32]; // into key for SSU }; RouterInfo (const char * filename); @@ -45,14 +54,17 @@ namespace data const char * GetIdentHashAbbreviation () const { return m_IdentHashAbbreviation; }; uint64_t GetTimestamp () const { return m_Timestamp; }; const std::vector
& GetAddresses () const { return m_Addresses; }; - Address * GetNTCPAddress (); + Address * GetNTCPAddress (bool v4only = true); + Address * GetSSUAddress (bool v4only = true); const RoutingKey& GetRoutingKey () const { return m_RoutingKey; }; void AddNTCPAddress (const char * host, int port); void SetProperty (const char * key, const char * value); const char * GetProperty (const char * key) const; bool IsFloodfill () const; - bool IsNTCP () 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 new file mode 100644 index 00000000..bfae3d0f --- /dev/null +++ b/SSU.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include "CryptoConst.h" +#include "Log.h" +#include "Timestamp.h" +#include "RouterContext.h" +#include "hmac.h" +#include "SSU.h" + +namespace i2p +{ +namespace ssu +{ + + 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::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"); + if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_REQUEST, buf, len)) + { + m_State = eSessionStateRequestReceived; + LogPrint ("Session request received"); + SendSessionCreated (senderEndpoint); + } + } + + void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) + { + LogPrint ("Process session created"); + if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_CREATED, buf, len)) + { + m_State = eSessionStateCreatedReceived; + LogPrint ("Session request received"); + // TODO: + } + } + + 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) = address->host.to_v4 ().to_ulong (); // network bytes order already + + 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 boost::asio::ip::udp::endpoint& senderEndpoint) + { + auto address = m_RemoteRouter ? m_RemoteRouter->GetSSUAddress () : nullptr; + if (!address) + { + LogPrint ("Missing remote SSU address"); + return; + } + + uint8_t buf[368 + 18]; + uint8_t * payload = buf + sizeof (SSUHeader); + memcpy (payload, i2p::context.GetRouterIdentity ().publicKey, 256); + + m_State = eSessionStateRequestSent; + m_Server->Send (buf, 368, m_RemoteEndpoint); + } + + bool SSUSession::ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, uint8_t * buf, size_t len) + { + auto address = i2p::context.GetRouterInfo ().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"); + 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); + 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::endpoint (boost::asio::ip::udp::v4 (), port)) + { + } + + SSUServer::~SSUServer () + { + for (auto it: m_Sessions) + delete it.second; + } + + void SSUServer::Start () + { + Receive (); + } + + void SSUServer::Stop () + { + 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, + boost::bind (&SSUServer::HandleReceivedFrom, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); + } + + void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (!ecode) + { + LogPrint ("SSU received ", bytes_transferred, " bytes"); + SSUSession * session = nullptr; + auto it = m_Sessions.find (m_SenderEndpoint); + if (it != m_Sessions.end ()) + session = it->second; + if (!session) + { + 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, 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 new file mode 100644 index 00000000..d8ae7090 --- /dev/null +++ b/SSU.h @@ -0,0 +1,116 @@ +#ifndef SSU_H__ +#define SSU_H__ + +#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 (3 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_RELAY_REQUEST = 3; + const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; + const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; + const uint8_t PAYLOAD_TYPE_DATA = 6; + const uint8_t PAYLOAD_TYPE_TEST = 7; + + enum SessionState + { + eSessionStateUnknown, + eSessionStateRequestSent, + eSessionStateRequestReceived, + eSessionStateCreatedSent, + eSessionStateCreatedReceived, + eSessionStateConfirmedSent, + eSessionStateConfirmedReceived, + eSessionStateEstablised + }; + + class SSUServer; + class SSUSession + { + public: + + 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 boost::asio::ip::udp::endpoint& senderEndpoint); + + bool ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, 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 + { + public: + + SSUServer (boost::asio::io_service& service, int port); + ~SSUServer (); + void Start (); + void Stop (); + SSUSession * GetSession (i2p::data::RouterInfo * router); + + void Send (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); + + private: + + void Receive (); + void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred); + + private: + + boost::asio::ip::udp::socket m_Socket; + boost::asio::ip::udp::endpoint m_SenderEndpoint; + uint8_t m_ReceiveBuffer[2*SSU_MTU]; + std::map m_Sessions; + }; +} +} + +#endif + diff --git a/Streaming.cpp b/Streaming.cpp index 99d51618..86f2a55c 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1,4 +1,3 @@ -#include "I2PEndian.h" #include #include #include @@ -26,30 +25,18 @@ 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; - + m_SendStreamID = packet->GetReceiveStreamID (); + // process flags + uint16_t flags = packet->GetFlags (); + const uint8_t * optionData = packet->GetOptionData (); if (flags & PACKET_FLAG_SYNCHRONIZE) { LogPrint ("Synchronize"); @@ -58,21 +45,21 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { LogPrint ("Signature"); - optionalData += 40; + optionData += 40; } if (flags & PACKET_FLAG_FROM_INCLUDED) { LogPrint ("From identity"); - optionalData += sizeof (i2p::data::Identity); + optionData += sizeof (i2p::data::Identity); } - // we have reached payload section + uint32_t receivedSeqn = packet->GetSeqn (); LogPrint ("seqn=", receivedSeqn, ", flags=", flags); if (!receivedSeqn || receivedSeqn == m_LastReceivedSequenceNumber + 1) { // we have received next message - packet->offset = buf - packet->buf; + packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) m_ReceiveQueue.Put (packet); else @@ -80,6 +67,26 @@ namespace stream m_LastReceivedSequenceNumber = receivedSeqn; SendQuickAck (); + + // 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 * packet = *it; + m_SavedPackets.erase (it++); + + LogPrint ("Process saved packet seqn=", packet->GetSeqn ()); + if (packet->GetLength () > 0) + m_ReceiveQueue.Put (packet); + else + delete packet; + m_LastReceivedSequenceNumber++; + SendQuickAck (); + } + else + break; + } } else { @@ -90,13 +97,14 @@ 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 } if (flags & PACKET_FLAG_CLOSE) @@ -107,6 +115,11 @@ namespace stream } } + void Stream::SavePacket (Packet * packet) + { + m_SavedPackets.insert (packet); + } + 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..5939d058 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,8 @@ namespace stream void ConnectAndSend (uint8_t * buf, size_t len); void SendQuickAck (); + + void SavePacket (Packet * packet); private: @@ -69,6 +92,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/Transports.cpp b/Transports.cpp index 371ea2e6..c3b96d78 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -12,7 +12,7 @@ namespace i2p Transports transports; Transports::Transports (): - m_Thread (0), m_Work (m_Service),m_NTCPAcceptor (0) + m_Thread (nullptr), m_Work (m_Service),m_NTCPAcceptor (nullptr), m_SSUServer (nullptr) { } @@ -34,11 +34,22 @@ namespace i2p m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); - LogPrint ("Start listening port ", address.port); + LogPrint ("Start listening TCP port ", address.port); auto conn = new i2p::ntcp::NTCPServerConnection (m_Service); m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this, conn, boost::asio::placeholders::error)); } + else if (address.transportStyle == RouterInfo::eTransportSSU) + { + if (!m_SSUServer) + { + m_SSUServer = new i2p::ssu::SSUServer (m_Service, address.port); + LogPrint ("Start listening UDP port ", address.port); + m_SSUServer->Start (); + } + else + LogPrint ("SSU server already exists"); + } } } @@ -49,6 +60,12 @@ namespace i2p m_NTCPSessions.clear (); delete m_NTCPAcceptor; + if (m_SSUServer) + { + m_SSUServer->Stop (); + delete m_SSUServer; + } + m_IsRunning = false; m_Service.stop (); if (m_Thread) @@ -139,7 +156,7 @@ namespace i2p auto address = r->GetNTCPAddress (); if (address) { - session = new i2p::ntcp::NTCPClient (m_Service, address->host.c_str (), address->port, *r); + session = new i2p::ntcp::NTCPClient (m_Service, address->host, address->port, *r); AddNTCPSession (session); } else diff --git a/Transports.h b/Transports.h index d6b08227..03f6cf35 100644 --- a/Transports.h +++ b/Transports.h @@ -7,6 +7,7 @@ #include #include #include "NTCPSession.h" +#include "SSU.h" #include "RouterInfo.h" #include "I2NPProtocol.h" @@ -47,6 +48,7 @@ namespace i2p boost::asio::ip::tcp::acceptor * m_NTCPAcceptor; std::map m_NTCPSessions; + i2p::ssu::SSUServer * m_SSUServer; public: diff --git a/Tunnel.cpp b/Tunnel.cpp index f92337f7..1163f47e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -395,16 +395,16 @@ namespace tunnel } else { - //OutboundTunnel * outboundTunnel = GetNextOutboundTunnel (); + 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 (), - i2p::data::netdb.GetRandomNTCPRouter () + firstHop, + i2p::data::netdb.GetRandomRouter (firstHop) }, - inboundTunnel->GetTunnelConfig ())/*, - outboundTunnel*/); + inboundTunnel->GetTunnelConfig ())); } } } @@ -450,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/Win32/.gitignore b/Win32/.gitignore new file mode 100644 index 00000000..d62f96f6 --- /dev/null +++ b/Win32/.gitignore @@ -0,0 +1,8 @@ +* +!*/ + +!*.sln +!*.vcproj +!*.vcxproj +!*.vcxproj.filters +!.gitignore diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index a98c7449..353e8afa 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -24,6 +24,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/Win32/i2pd.vcxproj.filters b/Win32/i2pd.vcxproj.filters index 39abceab..152edb53 100644 --- a/Win32/i2pd.vcxproj.filters +++ b/Win32/i2pd.vcxproj.filters @@ -72,6 +72,9 @@ Source Files + + Source Files + @@ -149,5 +152,8 @@ Header Files + + Header Files + \ No newline at end of file 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 new file mode 100644 index 00000000..df35a142 --- /dev/null +++ b/hmac.h @@ -0,0 +1,59 @@ +#ifndef HMAC_H__ +#define HMAC_H__ + +#include +#include +#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 +#include + +namespace i2p +{ +namespace crypto +{ + const uint64_t IPAD = 0x3636363636363636; + const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; + + inline void HMACMD5Digest (uint8_t * msg, size_t len, uint8_t * key, uint8_t * digest) + // key is 32 bytes + // digest is 16 bytes + // block size is 64 bytes + { + size_t totalLen = len + 64 + 32; + uint8_t buf[2048]; + // ikeypad + ((uint64_t *)buf)[0] = ((uint64_t *)key)[0] ^ IPAD; + ((uint64_t *)buf)[1] = ((uint64_t *)key)[1] ^ IPAD; + ((uint64_t *)buf)[2] = ((uint64_t *)key)[2] ^ IPAD; + ((uint64_t *)buf)[3] = ((uint64_t *)key)[3] ^ IPAD; + ((uint64_t *)buf)[4] = IPAD; + ((uint64_t *)buf)[5] = IPAD; + ((uint64_t *)buf)[6] = IPAD; + ((uint64_t *)buf)[7] = IPAD; + // concatenate with msg + memcpy (buf + 64, msg, len); + // calculate first hash + uint8_t hash[16]; // MD5 + CryptoPP::Weak1::MD5().CalculateDigest (hash, buf, len + 64); + + // okeypad + ((uint64_t *)buf)[0] = ((uint64_t *)key)[0] ^ OPAD; + ((uint64_t *)buf)[1] = ((uint64_t *)key)[1] ^ OPAD; + ((uint64_t *)buf)[2] = ((uint64_t *)key)[2] ^ OPAD; + ((uint64_t *)buf)[3] = ((uint64_t *)key)[3] ^ OPAD; + ((uint64_t *)buf)[4] = OPAD; + ((uint64_t *)buf)[5] = OPAD; + ((uint64_t *)buf)[6] = OPAD; + ((uint64_t *)buf)[7] = OPAD; + // 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); + + // calculate digest + CryptoPP::Weak1::MD5().CalculateDigest (digest, buf, totalLen); + } +} +} + +#endif + diff --git a/i2p.cpp b/i2p.cpp index e2b44dfe..67408fa6 100644 --- a/i2p.cpp +++ b/i2p.cpp @@ -10,10 +10,21 @@ #include "Tunnel.h" #include "NetDb.h" #include "HTTPServer.h" +#include "util.h" -int main( int, char** ) +int main( int argc, char* argv[] ) { - i2p::util::HTTPServer httpServer (7070); + i2p::util::ParseArguments(argc,argv); +#ifdef _WIN32 + setlocale(LC_CTYPE, ""); + SetConsoleCP(1251); + SetConsoleOutputCP(1251); + setlocale(LC_ALL, "Russian"); +#endif + + int httpport = i2p::util::GetIntArg("--httpport", 7070); + + i2p::util::HTTPServer httpServer (httpport); httpServer.Start (); i2p::data::netdb.Start (); diff --git a/portable_endian.h b/portable_endian.h new file mode 100644 index 00000000..3355d2b9 --- /dev/null +++ b/portable_endian.h @@ -0,0 +1,115 @@ +#ifndef PORTABLE_ENDIAN_H__ +#define PORTABLE_ENDIAN_H__ + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +# include + +#elif defined(__APPLE__) + +# include + +# define htobe16 OSSwapHostToBigInt16 +# define htole16 OSSwapHostToLittleInt16 +# define be16toh OSSwapBigToHostInt16 +# define le16toh OSSwapLittleToHostInt16 + +# define htobe32 OSSwapHostToBigInt32 +# define htole32 OSSwapHostToLittleInt32 +# define be32toh OSSwapBigToHostInt32 +# define le32toh OSSwapLittleToHostInt32 + +# define htobe64 OSSwapHostToBigInt64 +# define htole64 OSSwapHostToLittleInt64 +# define be64toh OSSwapBigToHostInt64 +# define le64toh OSSwapLittleToHostInt64 + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +# include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +# include + +# define be16toh betoh16 +# define le16toh letoh16 + +# define be32toh betoh32 +# define le32toh letoh32 + +# define be64toh betoh64 +# define le64toh letoh64 + +#elif defined(__WINDOWS__) + +#define INCL_EXTRA_HTON_FUNCTIONS +#define NOMINMAX +# include +#undef NOMINMAX +//# include + +# if BYTE_ORDER == LITTLE_ENDIAN + +# define htobe16 htons +# define htole16(x) (x) +# define be16toh ntohs +# define le16toh(x) (x) + +# define htobe32 htonl +# define htole32(x) (x) +# define be32toh ntohl +# define le32toh(x) (x) + +# define htobe64 htonll +# define htole64(x) (x) +# define be64toh ntohll +# define le64toh(x) (x) + +# elif BYTE_ORDER == BIG_ENDIAN + + /* that would be xbox 360 */ +# define htobe16(x) (x) +# define htole16(x) __builtin_bswap16(x) +# define be16toh(x) (x) +# define le16toh(x) __builtin_bswap16(x) + +# define htobe32(x) (x) +# define htole32(x) __builtin_bswap32(x) +# define be32toh(x) (x) +# define le32toh(x) __builtin_bswap32(x) + +# define htobe64(x) (x) +# define htole64(x) __builtin_bswap64(x) +# define be64toh(x) (x) +# define le64toh(x) __builtin_bswap64(x) + +# else + +# error byte order not supported + +# endif + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error platform not supported + +#endif + +#endif \ No newline at end of file diff --git a/util.cpp b/util.cpp new file mode 100644 index 00000000..1f000f50 --- /dev/null +++ b/util.cpp @@ -0,0 +1,45 @@ +#include "util.h" + +namespace i2p +{ +namespace util +{ +std::map mapArgs; + +void ParseArguments(int argc, const char* const argv[]) +{ + mapArgs.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); + } + if (strKey[0] != '-') + break; + + mapArgs[strKey] = strValue; + } +} + +int GetIntArg(const std::string& strArg, int nDefault) +{ + if (mapArgs.count(strArg)) + return atoi(mapArgs[strArg].c_str()); + return nDefault; +} + +std::string GetStringArg(const std::string& strArg, std::string nDefault) +{ + if (mapArgs.count(strArg)) + return mapArgs[strArg]; + return nDefault; +} + + +} // Namespace end +} diff --git a/util.h b/util.h new file mode 100644 index 00000000..ef015ded --- /dev/null +++ b/util.h @@ -0,0 +1,19 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +namespace i2p +{ +namespace util +{ + extern std::map mapArgs; + void ParseArguments(int argc, const char* const argv[]); + int GetIntArg(const std::string& strArg, int nDefault); + +} +} + + +#endif