diff --git a/.gitignore b/.gitignore index 2cb9ea10..0e609a70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # i2pd -*.o +obj/*.o router.info router.keys i2p diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 7f47601e..3905da5b 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -4,6 +4,7 @@ #include #include #include +#include "I2PEndian.h" #include "RouterInfo.h" namespace i2p @@ -19,6 +20,12 @@ namespace i2p uint8_t chks; }; + struct I2NPHeaderShort + { + uint8_t typeID; + uint32_t shortExpiration; + }; + struct I2NPDatabaseStoreMsg { uint8_t key[32]; @@ -101,6 +108,19 @@ namespace i2p len = offset + other.GetLength (); return *this; } + + // for SSU only + uint8_t * GetSSUHeader () { return buf + offset + sizeof(I2NPHeader) - sizeof(I2NPHeaderShort); }; + void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular + { + I2NPHeaderShort ssu = *(I2NPHeaderShort *)GetSSUHeader (); + I2NPHeader * header = GetHeader (); + header->typeID = ssu.typeID; + header->msgID = htobe32 (msgID); + header->expiration = htobe64 (be32toh (ssu.shortExpiration)*1000LL); + header->size = htobe16 (len - offset - sizeof (I2NPHeader)); + header->chks = 0; + } }; I2NPMessage * NewI2NPMessage (); void DeleteI2NPMessage (I2NPMessage * msg); diff --git a/I2PEndian.cpp b/I2PEndian.cpp index fa4e08cb..1fccf47f 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 3014f564..01ba73e8 100644 --- a/I2PEndian.h +++ b/I2PEndian.h @@ -5,16 +5,14 @@ #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); -#include "portable_endian.h" +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); #endif diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 2b75b463..b62a4598 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -57,13 +57,13 @@ namespace data LogPrint ("LeaseSet verification failed"); } - std::vector LeaseSet::GetNonExpiredLeases () const + std::set LeaseSet::GetNonExpiredLeases () const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - std::vector leases; + std::set leases; for (auto& it: m_Leases) if (ts < it.endDate) - leases.push_back (it); + leases.insert (it); return leases; } diff --git a/LeaseSet.h b/LeaseSet.h index 294e32d0..6bcc8122 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "Identity.h" namespace i2p @@ -18,6 +19,14 @@ namespace data uint8_t tunnelGateway[32]; uint32_t tunnelID; uint64_t endDate; + + bool operator< (const Lease& other) const + { + if (endDate != other.endDate) + return endDate > other.endDate; + else + return tunnelID < other.tunnelID; + } }; #pragma pack() @@ -34,7 +43,7 @@ namespace data const Identity& GetIdentity () const { return m_Identity; }; const IdentHash& GetIdentHash () const { return m_IdentHash; }; const std::vector& GetLeases () const { return m_Leases; }; - std::vector GetNonExpiredLeases () const; + std::set GetNonExpiredLeases () const; bool HasExpiredLeases () const; bool HasNonExpiredLeases () const; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; }; diff --git a/LittleBigEndian.h b/LittleBigEndian.h index ea879729..69f10ee9 100644 --- a/LittleBigEndian.h +++ b/LittleBigEndian.h @@ -59,12 +59,12 @@ struct LittleEndian return t; } - const T operator = (const T t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[i] = t >> (i << 3); - return t; - } + const T operator = (const T t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[sizeof(T)-1 - i] = static_cast(t >> (i << 3)); + return t; + } // operators diff --git a/Makefile b/Makefile index abe4238d..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 util.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 -lpthread +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/NTCPSession.cpp b/NTCPSession.cpp index 3e3d551d..e2686248 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -474,7 +474,9 @@ namespace ntcp if (ecode) { LogPrint ("Couldn't send msg: ", ecode.message ()); - Terminate (); + // we shouldn't call Terminate () here, because HandleReceive takes care + // TODO: 'delete this' statement in Terminate () must be eliminated later + // Terminate (); } else { diff --git a/NetDb.cpp b/NetDb.cpp index 278e8286..01234ad7 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "base64.h" #include "Log.h" @@ -13,12 +12,13 @@ #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) { @@ -41,10 +41,15 @@ namespace data 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) { } @@ -60,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)); } @@ -111,7 +123,7 @@ namespace data if (ts - lastTs >= 60) // save routers every minute { if (lastTs) - SaveUpdated ("netDb"); + SaveUpdated (m_NetDbPath); lastTs = ts; } } @@ -180,13 +192,12 @@ namespace data } // 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) + bool NetDb::CreateNetDb(boost::filesystem::path directory) { - boost::filesystem::path p (directory); - LogPrint (directory, " doesn't exist, trying to create it."); - if (!boost::filesystem::create_directory (p)) + LogPrint (directory.string(), " doesn't exist, trying to create it."); + if (!boost::filesystem::create_directory (directory)) { - LogPrint("Failed to create directory ", directory); + LogPrint("Failed to create directory ", directory.string()); return false; } @@ -200,19 +211,26 @@ namespace data #else suffix = std::string ("\\r") + chars[i]; #endif - if (!boost::filesystem::create_directory( boost::filesystem::path (p / suffix) )) return false; + 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); + boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); + p /= (directory); if (!boost::filesystem::exists (p)) { - if (!CreateNetDb(directory)) return; + // seems netDb doesn't exist yet + if (!CreateNetDb(p)) return; } - // TODO: Reseed if needed. + // 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) @@ -247,7 +265,14 @@ namespace data #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 (); @@ -255,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++; @@ -271,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++; } } @@ -601,45 +626,5 @@ 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 1397e9e4..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" @@ -76,10 +77,9 @@ namespace data private: - bool CreateNetDb(const char * directory); + 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; @@ -95,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/Queue.h b/Queue.h index ae60f099..316084a9 100644 --- a/Queue.h +++ b/Queue.h @@ -98,13 +98,18 @@ namespace util { public: - MsgQueue (): m_Thread (std::bind (&MsgQueue::Run, this)) {}; + MsgQueue (): m_Thread (std::bind (&MsgQueue::Run, this)) , running(1) {}; + void Stop() + { + running = 0; + m_Thread.join(); + } private: void Run () { Msg * msg = nullptr; - while ((msg = Queue::GetNext ()) != nullptr) + while ((msg = Queue::GetNext ()) != nullptr && running) { msg->Process (); delete msg; @@ -113,6 +118,7 @@ namespace util private: std::thread m_Thread; + int running; }; } } diff --git a/README.md b/README.md index ab64cb4d..20d84e5b 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,39 @@ 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. + +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 + + +Options +------- + +* --host= - The external IP +* --port= - The port to listen on +* --httpport= - The http port to listen on +* --log= - Enable or disable logging to file. 1 for yes, 0 for no. +* --daemon= - Eanble or disable daemon mode. 1 for yes, 0 for no. + + + 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 68f6508a..768b92a3 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -52,6 +52,13 @@ namespace i2p m_RouterInfo.CreateBuffer (); } + + void RouterContext::UpdateAddress (const char * host) + { + for (auto& address : m_RouterInfo.GetAddresses ()) + address.host = boost::asio::ip::address::from_string (host); + m_RouterInfo.CreateBuffer (); + } void RouterContext::Sign (uint8_t * buf, int len, uint8_t * signature) { diff --git a/RouterContext.h b/RouterContext.h index f43094c1..0cbd0bc7 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -28,6 +28,7 @@ namespace i2p void Sign (uint8_t * buf, int len, uint8_t * signature); void OverrideNTCPAddress (const char * host, int port); // temporary + void UpdateAddress (const char * host); // called from SSU private: diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 44bd90e4..f4668921 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -46,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 (); @@ -289,6 +294,14 @@ namespace data else return m_SupportedTransports & (eNTCPV4 | eNTCPV6); } + + bool RouterInfo::IsSSU (bool v4only) const + { + if (v4only) + return m_SupportedTransports & eSSUV4; + else + return m_SupportedTransports & (eSSUV4 | eSSUV6); + } RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) { diff --git a/RouterInfo.h b/RouterInfo.h index d7fc6dd9..3b28fee7 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -53,7 +53,7 @@ namespace data const char * GetIdentHashBase64 () const { return m_IdentHashBase64; }; const char * GetIdentHashAbbreviation () const { return m_IdentHashAbbreviation; }; uint64_t GetTimestamp () const { return m_Timestamp; }; - const std::vector
& GetAddresses () const { return m_Addresses; }; + std::vector
& GetAddresses () { return m_Addresses; }; Address * GetNTCPAddress (bool v4only = true); Address * GetSSUAddress (bool v4only = true); const RoutingKey& GetRoutingKey () const { return m_RoutingKey; }; @@ -63,6 +63,7 @@ namespace data const char * GetProperty (const char * key) const; bool IsFloodfill () const; bool IsNTCP (bool v4only = true) const; + bool IsSSU (bool v4only = true) const; bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; diff --git a/SSU.cpp b/SSU.cpp index bfae3d0f..57ebdce9 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -20,7 +20,7 @@ namespace ssu { } - void SSUSession::CreateAESKey (uint8_t * pubKey, uint8_t * aesKey) // TODO: move it to base class for NTCP and SSU + void SSUSession::CreateAESandMacKey (uint8_t * pubKey, uint8_t * aesKey, uint8_t * macKey) { CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); CryptoPP::SecByteBlock secretKey(dh.AgreedValueLength()); @@ -34,15 +34,24 @@ namespace ssu { aesKey[0] = 0; memcpy (aesKey + 1, secretKey, 31); + memcpy (macKey, secretKey + 31, 32); } - else + else + { memcpy (aesKey, secretKey, 32); + memcpy (macKey, secretKey + 32, 32); + } } void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { switch (m_State) { + case eSessionStateEstablished: + // most common case + ProcessMessage (buf, len); + break; + // establishing case eSessionStateUnknown: // session request ProcessSessionRequest (buf, len, senderEndpoint); @@ -51,33 +60,103 @@ namespace ssu // session created ProcessSessionCreated (buf, len); break; + case eSessionStateCreatedSent: + // session confirmed + ProcessSessionConfirmed (buf, len); + break; default: LogPrint ("SSU state not implemented yet"); } } + void SSUSession::ProcessMessage (uint8_t * buf, size_t len) + { + if (Validate (buf, len, m_MacKey)) + { + Decrypt (buf, len, m_SessionKey); + SSUHeader * header = (SSUHeader *)buf; + uint8_t payloadType = header->flag >> 4; + switch (payloadType) + { + case PAYLOAD_TYPE_DATA: + LogPrint ("SSU data received"); + ProcessData (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); + break; + case PAYLOAD_TYPE_TEST: + LogPrint ("SSU test received"); + break; + case PAYLOAD_TYPE_SESSION_DESTROYED: + LogPrint ("SSU session destroy received"); + break; + default: + LogPrint ("Unexpected SSU payload type ", (int)payloadType); + } + } + // TODO: try intro key as well + else + LogPrint ("MAC verifcation failed"); + } + 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)) + // use our intro key + if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_REQUEST, + i2p::context.GetRouterInfo (), buf, len)) { m_State = eSessionStateRequestReceived; LogPrint ("Session request received"); - SendSessionCreated (senderEndpoint); + m_RemoteEndpoint = senderEndpoint; + SendSessionCreated (buf + sizeof (SSUHeader)); } } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) { LogPrint ("Process session created"); - if (ProcessIntroKeyEncryptedMessage (PAYLOAD_TYPE_SESSION_CREATED, buf, len)) + 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 request received"); - // TODO: + LogPrint ("Session created received"); + uint8_t * ourAddress = buf + sizeof (SSUHeader) + 257; + boost::asio::ip::address_v4 ourIP (be32toh (*(uint32_t* )(ourAddress))); + uint16_t ourPort = be16toh (*(uint16_t *)(ourAddress + 4)); + LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP.to_string ().c_str ()); + uint32_t relayTag = be32toh (*(uint32_t *)(buf + sizeof (SSUHeader) + 263)); + SendSessionConfirmed (buf + sizeof (SSUHeader), ourAddress, relayTag); + m_State = eSessionStateEstablished; } } + void SSUSession::ProcessSessionConfirmed (uint8_t * buf, size_t len) + { + LogPrint ("Process session confirmed"); + if (Validate (buf, len, m_MacKey)) + { + Decrypt (buf, len, m_SessionKey); + SSUHeader * header = (SSUHeader *)buf; + if ((header->flag >> 4) == PAYLOAD_TYPE_SESSION_CONFIRMED) + { + m_State = eSessionStateConfirmedReceived; + LogPrint ("Session confirmed received"); + // TODO: + m_State = eSessionStateEstablished; + } + else + LogPrint ("Unexpected payload type ", (int)(header->flag >> 4)); + } + else + LogPrint ("MAC verifcation failed"); + } + void SSUSession::SendSessionRequest () { auto address = m_RemoteRouter ? m_RemoteRouter->GetSSUAddress () : nullptr; @@ -91,7 +170,7 @@ namespace ssu 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 + *(uint32_t *)(payload + 257) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); uint8_t iv[16]; CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); @@ -102,7 +181,53 @@ namespace ssu m_Server->Send (buf, 304, m_RemoteEndpoint); } - void SSUSession::SendSessionCreated (const boost::asio::ip::udp::endpoint& senderEndpoint) + 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) = htobe32 (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); + } + + void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, uint32_t relayTag) { auto address = m_RemoteRouter ? m_RemoteRouter->GetSSUAddress () : nullptr; if (!address) @@ -111,17 +236,46 @@ namespace ssu return; } - uint8_t buf[368 + 18]; + uint8_t buf[480 + 18]; uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, i2p::context.GetRouterIdentity ().publicKey, 256); + *payload = 1; // 1 fragment + payload++; // info + size_t identLen = sizeof (i2p::context.GetRouterIdentity ()); // 387 bytes + *(uint16_t *)(payload) = htobe16 (identLen); + payload += 2; // cursize + memcpy (payload, (uint8_t *)&i2p::context.GetRouterIdentity (), identLen); + payload += identLen; + uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); + *(uint32_t *)(payload) = htobe32 (signedOnTime); // signed on time + payload += 4; + size_t paddingSize = ((payload - buf) + 40)%16; + if (paddingSize > 0) paddingSize = 16 - paddingSize; + // TODO: fill padding + payload += paddingSize; // padding size - m_State = eSessionStateRequestSent; - m_Server->Send (buf, 368, m_RemoteEndpoint); + // signature + uint8_t signedData[532]; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time + memcpy (signedData, i2p::context.GetRouterIdentity ().publicKey, 256); // x + memcpy (signedData + 256, y, 256); // y + memcpy (signedData + 512, ourAddress, 6); // our address/port as seem by party + *(uint32_t *)(signedData + 518) = htobe32 (m_RemoteEndpoint.address ().to_v4 ().to_ulong ()); // remote IP + *(uint16_t *)(signedData + 522) = htobe16 (m_RemoteEndpoint.port ()); // remote port + *(uint32_t *)(signedData + 524) = htobe32 (relayTag); // relay tag + *(uint32_t *)(signedData + 528) = htobe32 (signedOnTime); // signed on time + i2p::context.Sign (signedData, 532, payload); // DSA signature + + uint8_t iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, 480, m_SessionKey, iv, m_MacKey); + m_State = eSessionStateConfirmedSent; + m_Server->Send (buf, 480, m_RemoteEndpoint); } - bool SSUSession::ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, uint8_t * buf, size_t len) + bool SSUSession::ProcessIntroKeyEncryptedMessage (uint8_t expectedPayloadType, i2p::data::RouterInfo& r, uint8_t * buf, size_t len) { - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + auto address = r.GetSSUAddress (); if (address) { // use intro key for verification and decryption @@ -131,7 +285,7 @@ namespace ssu SSUHeader * header = (SSUHeader *)buf; if ((header->flag >> 4) == expectedPayloadType) { - CreateAESKey (buf + sizeof (SSUHeader), m_SessionKey); + CreateAESandMacKey (buf + sizeof (SSUHeader), m_SessionKey, m_MacKey); return true; } else @@ -141,7 +295,7 @@ namespace ssu LogPrint ("MAC verifcation failed"); } else - LogPrint ("SSU is not supported"); + LogPrint ("SSU is not supported by ", r.GetIdentHashAbbreviation ()); return false; } @@ -177,6 +331,7 @@ namespace ssu 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); } @@ -203,14 +358,132 @@ namespace ssu SendSessionRequest (); } + void SSUSession::Close () + { + SendSesionDestroyed (); + } + 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)) + void SSUSession::ProcessData (uint8_t * buf, size_t len) { + //uint8_t * start = buf; + uint8_t flag = *buf; + buf++; + LogPrint ("Process SSU data flags=", (int)flag); + if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) + { + // explicit ACKs + uint8_t numAcks =*buf; + buf++; + // TODO: process ACKs + buf += numAcks*4; + } + if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) + { + // explicit ACK bitfields + uint8_t numBitfields =*buf; + buf++; + for (int i = 0; i < numBitfields; i++) + { + buf += 4; // msgID + // TODO: process ACH bitfields + while (*buf & 0x80) // not last + buf++; + buf++; // last byte + } + } + uint8_t numFragments = *buf; // number of fragments + buf++; + for (int i = 0; i < numFragments; i++) + { + uint32_t msgID = be32toh (*(uint32_t *)buf); // message ID + buf += 4; + uint8_t frag[4]; + frag[0] = 0; + memcpy (frag + 1, buf, 3); + buf += 3; + uint32_t fragmentInfo = be32toh (*(uint32_t *)frag); // fragment info + uint16_t fragmentSize = fragmentInfo & 0x1FFF; // bits 0 - 13 + bool isLast = fragmentInfo & 0x010000; // bit 16 + uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 + LogPrint ("SSU data fragment ", (int)fragmentNum, " of message ", msgID, " size=", (int)fragmentSize, isLast ? " last" : " non-last"); + I2NPMessage * msg = nullptr; + if (fragmentNum > 0) // follow-up fragment + { + auto it = m_IncomleteMessages.find (msgID); + if (it != m_IncomleteMessages.end ()) + { + msg = it->second; + memcpy (msg->buf + msg->len, buf, fragmentSize); + msg->len += fragmentSize; + } + else + // TODO: + LogPrint ("Unexpected follow-on fragment ", fragmentNum, " of message ", msgID); + } + else // first fragment + { + msg = NewI2NPMessage (); + memcpy (msg->GetSSUHeader (), buf, fragmentSize); + msg->len += fragmentSize - sizeof (I2NPHeaderShort); + } + + if (msg) + { + if (!fragmentNum && !isLast) + m_IncomleteMessages[msgID] = msg; + if (isLast) + { + if (fragmentNum > 0) + m_IncomleteMessages.erase (msgID); + msg->FromSSU (msgID); + i2p::HandleI2NPMessage (msg, false); + SendMsgAck (msgID); + } + } + buf += fragmentSize; + } + } + + void SSUSession::SendMsgAck (uint32_t msgID) + { + uint8_t buf[48 + 18]; // actual length is 44 = 37 + 7 but pad it to multiple of 16 + uint8_t iv[16]; + uint8_t * payload = buf + sizeof (SSUHeader); + *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag + payload++; + *payload = 1; // number of ACKs + payload++; + *(uint32_t *)(payload) = htobe32 (msgID); // msgID + payload += 4; + *payload = 0; // number of fragments + + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48, m_SessionKey, iv, m_MacKey); + m_Server->Send (buf, 48, m_RemoteEndpoint); + } + + void SSUSession::SendSesionDestroyed () + { + uint8_t buf[48 + 18], iv[16]; + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + rnd.GenerateBlock (iv, 16); // random iv + // encrypt message with session key + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48, m_SessionKey, iv, m_MacKey); + m_Server->Send (buf, 48, m_RemoteEndpoint); + } + + SSUServer::SSUServer (boost::asio::io_service& service, int port): + m_Endpoint (boost::asio::ip::udp::v4 (), port), m_Socket (service, m_Endpoint) + { + m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535)); + m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535)); } SSUServer::~SSUServer () @@ -289,6 +562,26 @@ namespace ssu } return session; } + + void SSUServer::DeleteSession (SSUSession * session) + { + if (session) + { + session->Close (); + m_Sessions.erase (session->GetRemoteEndpoint ()); + delete session; + } + } + + void SSUServer::DeleteAllSessions () + { + for (auto it: m_Sessions) + { + it.second->Close (); + delete it.second; + } + m_Sessions.clear (); + } } } diff --git a/SSU.h b/SSU.h index d8ae7090..15467262 100644 --- a/SSU.h +++ b/SSU.h @@ -26,7 +26,7 @@ namespace ssu const int SSU_MTU = 1484; - // payload types (3 bits) + // 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; @@ -35,6 +35,15 @@ namespace ssu const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; const uint8_t PAYLOAD_TYPE_DATA = 6; const uint8_t PAYLOAD_TYPE_TEST = 7; + const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; + + // data flags + const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; + const uint8_t DATA_FLAG_WANT_REPLY = 0x04; + const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; + const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; + const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; + const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; enum SessionState { @@ -45,7 +54,7 @@ namespace ssu eSessionStateCreatedReceived, eSessionStateConfirmedSent, eSessionStateConfirmedReceived, - eSessionStateEstablised + eSessionStateEstablished }; class SSUServer; @@ -58,18 +67,26 @@ namespace ssu void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void Connect (); + void Close (); + boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; void SendI2NPMessage (I2NPMessage * msg); private: - void CreateAESKey (uint8_t * pubKey, uint8_t * aesKey); // TODO: shouldn't be here + void CreateAESandMacKey (uint8_t * pubKey, uint8_t * aesKey, uint8_t * macKey); + void ProcessMessage (uint8_t * buf, size_t len); // call for established session 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 SendSessionCreated (const uint8_t * x); + void ProcessSessionConfirmed (uint8_t * buf, size_t len); + void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, uint32_t relayTag); + void ProcessData (uint8_t * buf, size_t len); + void SendMsgAck (uint32_t msgID); + void SendSesionDestroyed (); + + 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); @@ -82,7 +99,8 @@ namespace ssu SessionState m_State; CryptoPP::CBC_Mode::Encryption m_Encryption; CryptoPP::CBC_Mode::Decryption m_Decryption; - uint8_t m_SessionKey[32]; + uint8_t m_SessionKey[32], m_MacKey[32]; + std::map m_IncomleteMessages; }; class SSUServer @@ -94,7 +112,10 @@ namespace ssu void Start (); void Stop (); SSUSession * GetSession (i2p::data::RouterInfo * router); + void DeleteSession (SSUSession * session); + void DeleteAllSessions (); + 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: @@ -104,6 +125,7 @@ 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[2*SSU_MTU]; diff --git a/Streaming.cpp b/Streaming.cpp index 86f2a55c..05f838fa 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -34,55 +34,22 @@ namespace stream if (!m_SendStreamID) m_SendStreamID = packet->GetReceiveStreamID (); - // process flags - uint16_t flags = packet->GetFlags (); - 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); - } - uint32_t receivedSeqn = packet->GetSeqn (); - LogPrint ("seqn=", receivedSeqn, ", flags=", flags); + LogPrint ("Received seqn=", receivedSeqn); if (!receivedSeqn || receivedSeqn == m_LastReceivedSequenceNumber + 1) { - // we have received next message - packet->offset = packet->GetPayload () - 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 * packet = *it; + Packet * savedPacket = *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 (); + ProcessPacket (savedPacket); } else break; @@ -106,7 +73,47 @@ namespace stream SavePacket (packet); } } - + } + + 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"); @@ -114,11 +121,6 @@ namespace stream m_ReceiveQueue.WakeUp (); } } - - void Stream::SavePacket (Packet * packet) - { - m_SavedPackets.insert (packet); - } size_t Stream::Send (uint8_t * buf, size_t len, int timeout) { @@ -170,9 +172,10 @@ namespace stream if (!m_OutboundTunnel) m_OutboundTunnel = i2p::tunnel::tunnels.GetNextOutboundTunnel (); - if (m_OutboundTunnel) + auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (); + if (m_OutboundTunnel && !leases.empty ()) { - auto& lease = m_RemoteLeaseSet->GetLeases ()[0]; // TODO: + auto& lease = *leases.begin (); // TODO: m_OutboundTunnel->SendTunnelDataMsg (lease.tunnelGateway, lease.tunnelID, msg); } else @@ -206,7 +209,7 @@ namespace stream auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (); if (!leases.empty ()) { - auto& lease = leases[0]; // TODO: + auto& lease = *leases.begin (); // TODO: m_OutboundTunnel->SendTunnelDataMsg (lease.tunnelGateway, lease.tunnelID, msg); LogPrint ("Quick Ack sent"); } @@ -249,11 +252,12 @@ namespace stream I2NPMessage * msg = i2p::garlic::routing.WrapSingleMessage (m_RemoteLeaseSet, CreateDataMessage (this, packet, size)); - if (m_OutboundTunnel) + auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (); + if (m_OutboundTunnel && !leases.empty ()) { - auto& lease = m_RemoteLeaseSet->GetLeases ()[0]; // TODO: + auto& lease = *leases.begin (); // TODO: m_OutboundTunnel->SendTunnelDataMsg (lease.tunnelGateway, lease.tunnelID, msg); - LogPrint ("FIN sent"); + LogPrint ("FIN sent"); } else DeleteI2NPMessage (msg); diff --git a/Streaming.h b/Streaming.h index 5939d058..18d09b5d 100644 --- a/Streaming.h +++ b/Streaming.h @@ -84,6 +84,7 @@ namespace stream void SendQuickAck (); void SavePacket (Packet * packet); + void ProcessPacket (Packet * packet); private: diff --git a/Transports.cpp b/Transports.cpp index c3b96d78..8ea6c05f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -25,6 +25,7 @@ namespace i2p { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); + m_Timer = new boost::asio::deadline_timer (m_Service); // create acceptors auto addresses = context.GetRouterInfo ().GetAddresses (); for (auto& address : addresses) @@ -38,9 +39,11 @@ namespace i2p auto conn = new i2p::ntcp::NTCPServerConnection (m_Service); m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this, conn, boost::asio::placeholders::error)); - } + // temporary always run SSU server + // TODO: uncomment following lines later + /*} else if (address.transportStyle == RouterInfo::eTransportSSU) - { + {*/ if (!m_SSUServer) { m_SSUServer = new i2p::ssu::SSUServer (m_Service, address.port); @@ -51,6 +54,9 @@ namespace i2p LogPrint ("SSU server already exists"); } } + + // TODO: do it for SSU only + DetectExternalIP (); } void Transports::Stop () @@ -59,7 +65,10 @@ namespace i2p delete session.second; m_NTCPSessions.clear (); delete m_NTCPAcceptor; - + + m_Timer->cancel (); + delete m_Timer; + if (m_SSUServer) { m_SSUServer->Stop (); @@ -173,4 +182,30 @@ namespace i2p else LogPrint ("Session not found"); } + + void Transports::DetectExternalIP () + { + for (int i = 0; i < 5; i ++) + { + auto router = i2p::data::netdb.GetRandomRouter (); + if (router && router->IsSSU () && m_SSUServer) + m_SSUServer->GetSession (const_cast(router)); //TODO + } + if (m_Timer) + { + m_Timer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds + m_Timer->async_wait (boost::bind (&Transports::HandleTimer, this, boost::asio::placeholders::error)); + } + } + + void Transports::HandleTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + // end of external IP detection + if (m_SSUServer) + m_SSUServer->DeleteAllSessions (); + } + } + } diff --git a/Transports.h b/Transports.h index 03f6cf35..12bd8f21 100644 --- a/Transports.h +++ b/Transports.h @@ -38,6 +38,9 @@ namespace i2p void Run (); void HandleAccept (i2p::ntcp::NTCPServerConnection * conn, const boost::system::error_code& error); void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); + + void DetectExternalIP (); + void HandleTimer (const boost::system::error_code& ecode); private: @@ -49,6 +52,7 @@ namespace i2p std::map m_NTCPSessions; i2p::ssu::SSUServer * m_SSUServer; + boost::asio::deadline_timer * m_Timer; public: diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index 353e8afa..c6f95ed7 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -22,6 +22,7 @@ + @@ -31,6 +32,7 @@ + @@ -47,6 +49,7 @@ + @@ -59,6 +62,7 @@ + {930568EC-31C9-406A-AD1C-9636DF5D8FAA} diff --git a/Win32/i2pd.vcxproj.filters b/Win32/i2pd.vcxproj.filters index 152edb53..0eb7dc3d 100644 --- a/Win32/i2pd.vcxproj.filters +++ b/Win32/i2pd.vcxproj.filters @@ -75,6 +75,12 @@ Source Files + + Source Files + + + Source Files + @@ -155,5 +161,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/hmac.h b/hmac.h index df35a142..6e9c042b 100644 --- a/hmac.h +++ b/hmac.h @@ -18,7 +18,6 @@ namespace crypto // 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; @@ -47,10 +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); + CryptoPP::Weak1::MD5().CalculateDigest (digest, buf, 96); } } } diff --git a/i2p.cpp b/i2p.cpp index 67408fa6..97914b5e 100644 --- a/i2p.cpp +++ b/i2p.cpp @@ -1,6 +1,18 @@ +#ifdef _WIN32 +#define _CRT_SECURE_NO_WARNINGS // to use freopen +#endif + #include #include #include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + #include "Log.h" #include "base64.h" #include "Transports.h" @@ -12,9 +24,43 @@ #include "HTTPServer.h" #include "util.h" + +// Global +int running = 1; +int isDaemon; + +#ifndef _WIN32 +void handle_signal(int sig) +{ + switch (sig) + { + case SIGHUP: + if (i2p::util::config::GetArg("daemon", 0) == 1) + { + static bool first=true; + if (first) + { + first=false; + return; + } + } + LogPrint("Reloading config."); + i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); + break; + case SIGABRT: + case SIGTERM: + case SIGINT: + running = 0; // Exit loop + break; + } +} +#endif + + int main( int argc, char* argv[] ) { - i2p::util::ParseArguments(argc,argv); + i2p::util::config::OptionParser(argc,argv); + isDaemon = i2p::util::config::GetArg("-daemon", 0); #ifdef _WIN32 setlocale(LC_CTYPE, ""); SetConsoleCP(1251); @@ -22,19 +68,110 @@ int main( int argc, char* argv[] ) setlocale(LC_ALL, "Russian"); #endif - int httpport = i2p::util::GetIntArg("--httpport", 7070); - i2p::util::HTTPServer httpServer (httpport); + 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); - httpServer.Start (); + int isLogging = i2p::util::config::GetArg("-log", 0); + if (isLogging == 1) + { + std::string logfile = i2p::util::filesystem::GetDataDir().string(); +#ifndef _WIN32 + logfile.append("/debug.log"); +#else + logfile.append("\\debug.log"); +#endif + freopen(logfile.c_str(),"a",stdout); + LogPrint("Logging to file enabled."); + } + + +#ifndef _WIN32 + if (isDaemon == 1) + { + pid_t pid; + pid = fork(); + if (pid > 0) + { + g_Log.Stop(); + return 0; + } + if (pid < 0) + { + return -1; + } + + umask(0); + int sid = setsid(); + if (sid < 0) + { + LogPrint("Error, could not create process group."); + return -1; + } + chdir(i2p::util::filesystem::GetDataDir().string().c_str()); + } + + // Pidfile + std::string pidfile = i2p::util::filesystem::GetDataDir().string(); + pidfile.append("/i2pd.pid"); + int pidFilehandle = open(pidfile.c_str(), O_RDWR|O_CREAT, 0600); + if (pidFilehandle == -1 ) + { + LogPrint("Error, could not create pid file (", pidfile, ")\nIs an instance already running?"); + return -1; + } + if (lockf(pidFilehandle,F_TLOCK,0) == -1) + { + LogPrint("Error, could not lock pid file (", pidfile, ")\nIs an instance already running?"); + return -1; + } + char pid[10]; + sprintf(pid,"%d\n",getpid()); + write(pidFilehandle, pid, strlen(pid)); + + // Signal handler + struct sigaction sa; + sa.sa_handler = handle_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGHUP,&sa,0); + sigaction(SIGABRT,&sa,0); + sigaction(SIGTERM,&sa,0); + sigaction(SIGINT,&sa,0); +#endif + + //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)); + + i2p::util::HTTPServer httpServer (i2p::util::config::GetArg("-httpport", 7070)); + + httpServer.Start (); i2p::data::netdb.Start (); - i2p::transports.Start (); - i2p::tunnel::tunnels.Start (); - - std::this_thread::sleep_for (std::chrono::seconds(10000)); - i2p::tunnel::tunnels.Stop (); - i2p::transports.Stop (); - i2p::data::netdb.Stop (); - httpServer.Stop (); + i2p::transports.Start (); + i2p::tunnel::tunnels.Start (); + + while (running) + { + //TODO Meeh: Find something better to do here. + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + LogPrint("Shutdown started."); + + i2p::tunnel::tunnels.Stop (); + i2p::transports.Stop (); + i2p::data::netdb.Stop (); + httpServer.Stop (); + + if (isLogging == 1) + { + fclose (stdout); + } +#ifndef _WIN32 + close(pidFilehandle); + unlink(pidfile.c_str()); +#endif return 0; } diff --git a/portable_endian.h b/portable_endian.h deleted file mode 100644 index 3355d2b9..00000000 --- a/portable_endian.h +++ /dev/null @@ -1,115 +0,0 @@ -#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 index 1f000f50..1917a984 100644 --- a/util.cpp +++ b/util.cpp @@ -1,45 +1,269 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "util.h" +#include "Log.h" + +#ifdef WIN32 +#include +#include +#endif namespace i2p { namespace util { -std::map mapArgs; -void ParseArguments(int argc, const char* const argv[]) +namespace config { - 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; + std::map mapArgs; + std::map > mapMultiArgs; - mapArgs[strKey] = strValue; - } + 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; + } } -int GetIntArg(const std::string& strArg, int nDefault) +namespace filesystem { - if (mapArgs.count(strArg)) - return atoi(mapArgs[strArg].c_str()); - return nDefault; + 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 + char localAppData[MAX_PATH]; + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + return boost::filesystem::path(std::string(localAppData) + "\\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 + } } -std::string GetStringArg(const std::string& strArg, std::string nDefault) +namespace http { - if (mapArgs.count(strArg)) - return mapArgs[strArg]; - return nDefault; + 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 index ef015ded..519e51b3 100644 --- a/util.h +++ b/util.h @@ -3,15 +3,45 @@ #include #include +#include +#include + +#define PAIRTYPE(t1, t2) std::pair namespace i2p { namespace util { - extern std::map mapArgs; - void ParseArguments(int argc, const char* const argv[]); - int GetIntArg(const std::string& strArg, int nDefault); + 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_; + }; + } } }