diff --git a/.gitignore b/.gitignore index 89a17a3c..b6cffd15 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,4 @@ pip-log.txt # Sphinx docs/_build +/androidIdea/ diff --git a/AddressBook.cpp b/AddressBook.cpp index 3cf2bc56..cd15d68d 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -11,6 +11,7 @@ #include "Identity.h" #include "FS.h" #include "Log.h" +#include "HTTP.h" #include "NetDb.h" #include "ClientContext.h" #include "AddressBook.h" @@ -339,12 +340,12 @@ namespace client std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { - LoadHostsFromStream (f); + LoadHostsFromStream (f, false); m_IsLoaded = true; } } - bool AddressBook::LoadHostsFromStream (std::istream& f) + bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; @@ -365,17 +366,18 @@ namespace client std::string addr = s.substr(pos); auto ident = std::make_shared (); - if (ident->FromBase64(addr)) - { - m_Addresses[name] = ident->GetIdentHash (); - m_Storage->AddAddress (ident); - numAddresses++; - } - else - { + if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); incomplete = f.eof (); + continue; } + numAddresses++; + if (m_Addresses.count(name) > 0) + continue; /* already exists */ + m_Addresses[name] = ident->GetIdentHash (); + m_Storage->AddAddress (ident); + if (is_update) + LogPrint(eLogInfo, "Addressbook: added new host: ", name); } else incomplete = f.eof (); @@ -516,14 +518,15 @@ namespace client if (!m_DefaultSubscription) m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS)); m_IsDownloading = true; - m_DefaultSubscription->CheckSubscription (); + m_DefaultSubscription->CheckUpdates (); } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; - m_Subscriptions[ind]->CheckSubscription (); + std::thread load_hosts(&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]); + load_hosts.detach(); // TODO: use join } } else @@ -569,7 +572,7 @@ namespace client ident = FindAddress (address.substr (dot + 1)); if (!ident) { - LogPrint (eLogError, "AddressBook: Can't find domain for ", address); + LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; } @@ -585,7 +588,7 @@ namespace client std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } - LogPrint (eLogDebug, "AddressBook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); + LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); size_t len = address.length () + 9; uint8_t * buf = new uint8_t[len]; memset (buf, 0, 4); @@ -602,11 +605,11 @@ namespace client { if (len < 44) { - LogPrint (eLogError, "AddressBook: Lookup response is too short ", len); + LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); return; } uint32_t nonce = bufbe32toh (buf + 4); - LogPrint (eLogDebug, "AddressBook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); + LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); std::string address; { std::unique_lock l(m_LookupsMutex); @@ -629,168 +632,153 @@ namespace client { } - void AddressBookSubscription::CheckSubscription () + void AddressBookSubscription::CheckUpdates () { - std::thread load_hosts(&AddressBookSubscription::Request, this); - load_hosts.detach(); // TODO: use join + bool result = MakeRequest (); + m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } - void AddressBookSubscription::Request () + bool AddressBookSubscription::MakeRequest () { + i2p::http::URL url; // must be run in separate thread - LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - bool success = false; - i2p::util::http::url u (m_Link); - i2p::data::IdentHash ident; - if (m_Book.GetIdentHash (u.host_, ident)) + LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); + if (!url.parse(m_Link)) { + LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); + return false; + } + if (!m_Book.GetIdentHash (url.host, m_Ident)) { + LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); + return false; + } + /* this code block still needs some love */ + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); + if (!leaseSet) { - if (!m_Etag.length ()) - { - // load ETag - m_Book.GetEtag (ident, m_Etag, m_LastModified); - LogPrint (eLogInfo, "Addressbook: set ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - } - std::condition_variable newDataReceived; - std::mutex newDataReceivedMutex; - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); - if (!leaseSet) + std::unique_lock l(newDataReceivedMutex); + i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, + [&newDataReceived, &leaseSet](std::shared_ptr ls) + { + leaseSet = ls; + newDataReceived.notify_all (); + }); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { - std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident, - [&newDataReceived, &leaseSet](std::shared_ptr ls) - { - leaseSet = ls; - newDataReceived.notify_all (); - }); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - { - LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); - i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident); - } + LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident); + return false; } - if (leaseSet) - { - std::stringstream request, response; - // standard header - request << "GET " << u.path_ << " HTTP/1.1\r\n" - << "Host: " << u.host_ << "\r\n" - << "Accept: */*\r\n" - << "User-Agent: Wget/1.11.4\r\n" - //<< "Accept-Encoding: gzip\r\n" - << "X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n" - << "Connection: close\r\n"; - if (m_Etag.length () > 0) // etag - request << i2p::util::http::IF_NONE_MATCH << ": " << m_Etag << "\r\n"; - if (m_LastModified.length () > 0) // if-modfief-since - request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n"; - request << "\r\n"; // end of header - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_); - stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); - - uint8_t buf[4096]; - bool end = false; - while (!end) - { - stream->AsyncReceive (boost::asio::buffer (buf, 4096), - [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (bytes_transferred) - response.write ((char *)buf, bytes_transferred); - if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) - end = true; - newDataReceived.notify_all (); - }, - 30); // wait for 30 seconds - std::unique_lock l(newDataReceivedMutex); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); - } - // process remaining buffer - while (size_t len = stream->ReadSome (buf, 4096)) - response.write ((char *)buf, len); - - // parse response - std::string version; - response >> version; // HTTP version - int status = 0; - response >> status; // status - if (status == 200) // OK + } + if (!leaseSet) { + /* still no leaseset found */ + LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); + return false; + } + if (m_Etag.empty() && m_LastModified.empty()) { + m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); + LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); + } + /* save url parts for later use */ + std::string dest_host = url.host; + int dest_port = url.port ? url.port : 80; + /* create http request & send it */ + i2p::http::HTTPReq req; + req.add_header("Host", dest_host); + req.add_header("User-Agent", "Wget/1.11.4"); + req.add_header("Connection", "close"); + if (!m_Etag.empty()) + req.add_header("If-None-Match", m_Etag); + if (!m_LastModified.empty()) + req.add_header("If-Modified-Since", m_LastModified); + /* convert url to relative */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); + std::string request = req.to_string(); + stream->Send ((const uint8_t *) request.data(), request.length()); + /* read response */ + std::string response; + uint8_t recv_buf[4096]; + bool end = false; + while (!end) { + stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), + [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) { - bool isChunked = false, isGzip = false; - m_Etag = ""; m_LastModified = ""; - std::string header, statusMessage; - std::getline (response, statusMessage); - // read until new line meaning end of header - while (!response.eof () && header != "\r") - { - std::getline (response, header); - if (response.fail ()) break; - auto colon = header.find (':'); - if (colon != std::string::npos) - { - std::string field = header.substr (0, colon); - boost::to_lower (field); // field are not case-sensitive - colon++; - header.resize (header.length () - 1); // delete \r - if (field == i2p::util::http::ETAG) - m_Etag = header.substr (colon + 1); - else if (field == i2p::util::http::LAST_MODIFIED) - m_LastModified = header.substr (colon + 1); - else if (field == i2p::util::http::TRANSFER_ENCODING) - isChunked = !header.compare (colon + 1, std::string::npos, "chunked"); - else if (field == i2p::util::http::CONTENT_ENCODING) - isGzip = !header.compare (colon + 1, std::string::npos, "gzip") || - !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); - } - } - LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - if (!response.eof () && !response.fail ()) - { - if (!isChunked) - success = ProcessResponse (response, isGzip); - else - { - // merge chunks - std::stringstream merged; - i2p::util::http::MergeChunkedResponse (response, merged); - success = ProcessResponse (merged, isGzip); - } - } - } - else if (status == 304) - { - success = true; - LogPrint (eLogInfo, "Addressbook: no updates from ", m_Link); - } - else - LogPrint (eLogWarning, "Adressbook: HTTP response ", status); - } - else - LogPrint (eLogError, "Addressbook: address ", u.host_, " not found"); + if (bytes_transferred) + response.append ((char *)recv_buf, bytes_transferred); + if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) + end = true; + newDataReceived.notify_all (); + }, + 30); // wait for 30 seconds + std::unique_lock l(newDataReceivedMutex); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) + LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); } - else - LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_); - - if (!success) - LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); - - m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified); - } - - bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip) - { - if (isGzip) - { - std::stringstream uncompressed; + // process remaining buffer + while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) { + response.append ((char *)recv_buf, len); + } + /* parse response */ + i2p::http::HTTPRes res; + int res_head_len = res.parse(response); + if (res_head_len < 0) { + LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); + return false; + } + if (res_head_len == 0) { + LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); + return false; + } + /* assert: res_head_len > 0 */ + response.erase(0, res_head_len); + if (res.code == 304) { + LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); + return false; + } + if (res.code != 200) { + LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); + return false; + } + int len = res.content_length(); + if (response.empty()) { + LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); + return false; + } + if (len > 0 && len != (int) response.length()) { + LogPrint(eLogError, "Addressbook: response size mismatch, expected: ", response.length(), ", got: ", len, "bytes"); + return false; + } + /* assert: res.code == 200 */ + auto it = res.headers.find("ETag"); + if (it != res.headers.end()) { + m_Etag = it->second; + } + it = res.headers.find("If-Modified-Since"); + if (it != res.headers.end()) { + m_LastModified = it->second; + } + if (res.is_chunked()) { + std::stringstream in(response), out; + i2p::http::MergeChunkedResponse (in, out); + response = out.str(); + } else if (res.is_gzipped()) { + std::stringstream out; i2p::data::GzipInflator inflator; - inflator.Inflate (s, uncompressed); - if (!uncompressed.fail ()) - return m_Book.LoadHostsFromStream (uncompressed); - else + inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); + if (out.fail()) { + LogPrint(eLogError, "Addressbook: can't gunzip http response"); return false; - } - else - return m_Book.LoadHostsFromStream (s); + } + response = out.str(); + } + std::stringstream ss(response); + LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); + m_Book.LoadHostsFromStream (ss, true); + return true; } AddressResolver::AddressResolver (std::shared_ptr destination): @@ -821,7 +809,7 @@ namespace client { if (len < 9 || len < buf[8] + 9U) { - LogPrint (eLogError, "AddressBook: Address request is too short ", len); + LogPrint (eLogError, "Addressbook: Address request is too short ", len); return; } // read requested address @@ -829,7 +817,7 @@ namespace client char address[255]; memcpy (address, buf + 9, l); address[l] = 0; - LogPrint (eLogDebug, "AddressBook: Address request ", address); + LogPrint (eLogDebug, "Addressbook: Address request ", address); // send response uint8_t response[44]; memset (response, 0, 4); // reserved diff --git a/AddressBook.h b/AddressBook.h index d67089fa..fd852907 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -70,7 +70,7 @@ namespace client void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (std::shared_ptr address); - bool LoadHostsFromStream (std::istream& f); + bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } @@ -112,17 +112,17 @@ namespace client public: AddressBookSubscription (AddressBook& book, const std::string& link); - void CheckSubscription (); + void CheckUpdates (); private: - void Request (); - bool ProcessResponse (std::stringstream& s, bool isGzip = false); + bool MakeRequest (); private: AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; + i2p::data::IdentHash m_Ident; // m_Etag must be surrounded by "" }; diff --git a/BOB.cpp b/BOB.cpp index af1fb19a..f983c67a 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -202,9 +202,9 @@ namespace client } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): - m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0), - m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0), - m_CurrentDestination (nullptr) + m_Owner (owner), m_Socket (m_Owner.GetService ()), + m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), + m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } @@ -364,9 +364,9 @@ namespace client if (m_OutPort && !m_Address.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); - SendReplyOK ("tunnel starting"); + SendReplyOK ("tunnel starting"); } - + void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { auto dest = m_Owner.FindDestination (m_Nickname); @@ -523,6 +523,34 @@ namespace client else SendReplyError ("malformed"); } + + void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) + { + LogPrint (eLogDebug, "BOB: status ", operand); + if (operand == m_Nickname) + { + std::stringstream s; + s << "DATA"; s << " NICKNAME:"; s << operand; + if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) + s << " STARTING:false RUNNING:true STOPPING:false"; + else + s << " STARTING:true RUNNING:false STOPPING:false"; + s << " KEYS: true"; s << " QUIET:"; s << (m_IsQuiet ? "true":"false"); + if (m_InPort) + { + s << " INPORT:" << m_InPort; + s << " INHOST:" << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + } + if (m_OutPort) + { + s << " OUTPORT:" << m_OutPort; + s << " OUTHOST:" << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + } + SendReplyOK (s.str().c_str()); + } + else + SendReplyError ("no nickname has been set"); + } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), @@ -548,6 +576,7 @@ namespace client m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; + m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; } BOBCommandChannel::~BOBCommandChannel () diff --git a/BOB.h b/BOB.h index b73e390e..d2118c5c 100644 --- a/BOB.h +++ b/BOB.h @@ -36,6 +36,7 @@ namespace client const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; + const char BOB_COMMAND_STATUS[] = "status"; const char BOB_VERSION[] = "BOB 00.00.10\nOK\n"; const char BOB_REPLY_OK[] = "OK %s\n"; @@ -168,6 +169,7 @@ namespace client void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); + void StatusCommandHandler (const char * operand, size_t len); private: diff --git a/Config.cpp b/Config.cpp index 27f0fe1d..2a35ce7e 100644 --- a/Config.cpp +++ b/Config.cpp @@ -31,6 +31,7 @@ namespace config { #ifdef MESHNET nat = false; #endif + options_description general("General options"); general.add_options() ("help", "Show this message") @@ -126,6 +127,16 @@ namespace config { ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; + bool upnp_default = false; +#if (defined(USE_UPNP) && (defined(WIN32_APP) || defined(ANDROID))) + upnp_default = true; // enable UPNP for windows GUI and android by default +#endif + options_description upnp("UPnP options"); + upnp.add_options() + ("upnp.enabled", value()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding") + ("upnp.name", value()->default_value("I2Pd"), "Name i2pd appears in UPnP forwardings list") + ; + options_description precomputation("Precomputation options"); precomputation.add_options() ("precomputation.elgamal", @@ -153,6 +164,7 @@ namespace config { .add(bob) .add(i2cp) .add(i2pcontrol) + .add(upnp) .add(precomputation) .add(trust) ; diff --git a/Daemon.cpp b/Daemon.cpp index ab2c052f..7dec30a9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -22,11 +22,8 @@ #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" -#include "util.h" - -#ifdef USE_UPNP #include "UPnP.h" -#endif +#include "util.h" namespace i2p { @@ -40,10 +37,7 @@ namespace i2p std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; - -#ifdef USE_UPNP - i2p::transport::UPnP m_UPnP; -#endif + std::unique_ptr UPnP; }; Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} @@ -130,47 +124,16 @@ namespace i2p ipv4 = false; ipv6 = true; #endif - - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - - bool nat; i2p::config::GetOption("nat", nat); - if (nat) - { - LogPrint(eLogInfo, "Daemon: assuming be are behind NAT"); - // we are behind nat, try setting via host - std::string host; i2p::config::GetOption("host", host); - if (!i2p::config::IsDefault("host")) - { - LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); - } - } - else - { - // we are not behind nat - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ifname.size()) - { - // bind to interface, we have no NAT so set external address too - auto addr = i2p::util::net::GetInterfaceAddress(ifname, ipv6); - LogPrint(eLogInfo, "Daemon: bind to network interface ", ifname, " with public address ", addr); - i2p::context.UpdateAddress(addr); - } - } - - uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) - { + { LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); i2p::context.UpdatePort (port); - } - - - bool transit; i2p::config::GetOption("notransit", transit); + } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); + + bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); SetMaxNumTransitTunnels (transitTunnels); @@ -249,21 +212,24 @@ namespace i2p LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); -#ifdef USE_UPNP - LogPrint(eLogInfo, "Daemon: starting UPnP"); - d.m_UPnP.Start (); -#endif + bool upnp; i2p::config::GetOption("upnp.enabled", upnp); + if (upnp) { + d.UPnP = std::unique_ptr(new i2p::transport::UPnP); + d.UPnP->Start (); + } + bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); - if(!ssu) LogPrint(eLogDebug, "Daemon: ssu disabled"); - if(!ntcp) LogPrint(eLogDebug, "Daemon: ntcp disabled"); + if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); + if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); i2p::transport::transports.Start(ntcp, ssu); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ + i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); return false; } @@ -304,10 +270,12 @@ namespace i2p i2p::client::context.Stop(); LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); -#ifdef USE_UPNP - LogPrint(eLogInfo, "Daemon: stopping UPnP"); - d.m_UPnP.Stop (); -#endif + + if (d.UPnP) { + d.UPnP->Stop (); + d.UPnP = nullptr; + } + LogPrint(eLogInfo, "Daemon: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "Daemon: stopping NetDB"); diff --git a/Daemon.h b/Daemon.h index fa4f47ec..85b31240 100644 --- a/Daemon.h +++ b/Daemon.h @@ -45,6 +45,20 @@ namespace i2p } }; +#elif defined(ANDROID) +#define Daemon i2p::util::DaemonAndroid::Instance() + // dummy, invoked from android/jni/DaemonAndroid.* + class DaemonAndroid: public i2p::util::Daemon_Singleton + { + public: + + static DaemonAndroid& Instance() + { + static DaemonAndroid instance; + return instance; + } + }; + #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton diff --git a/Destination.cpp b/Destination.cpp index abfe3227..2df14a9f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -470,7 +470,7 @@ namespace client else LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); // we have to publish again - s->Publish (); + s->Publish (); }); } } diff --git a/HTTP.cpp b/HTTP.cpp index 27421241..4a0286a7 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -278,6 +278,15 @@ namespace http { return false; } + bool HTTPRes::is_gzipped() { + auto it = headers.find("Content-Encoding"); + if (it == headers.end()) + return false; /* no header */ + if (it->second.find("gzip") != std::string::npos) + return true; /* gotcha! */ + return false; + } + long int HTTPMsg::content_length() { unsigned long int length = 0; auto it = headers.find("Content-Length"); diff --git a/HTTP.h b/HTTP.h index bce55026..847cf347 100644 --- a/HTTP.h +++ b/HTTP.h @@ -118,6 +118,9 @@ namespace http { /** @brief Checks that response declared as chunked data */ bool is_chunked(); + + /** @brief Checks that response contains compressed data */ + bool is_gzipped(); }; /** diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index db58d1dc..b35c3b4a 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -23,6 +22,21 @@ namespace i2p { namespace proxy { + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + }; + + static const char *pageHead = + "\r\n" + " I2P HTTP proxy: error\r\n" + " \r\n" + "\r\n" + ; + bool str_rmatch(std::string & str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) @@ -32,51 +46,34 @@ namespace proxy { return false; } - static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: - enum state - { - GET_METHOD, - GET_HOSTNAME, - GET_HTTPV, - GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed - DONE - }; - - void EnterState(state nstate); - bool HandleData(uint8_t *http_buff, std::size_t len); + + bool HandleRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(const char *message); - void RedirectToJumpService(std::string & host); - void ExtractRequest(); - bool ValidateHTTPRequest(); - void HandleJumpServices(); - bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); + void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); + /* error helpers */ + void GenericProxyError(const char *title, const char *description); + void HostNotFound(std::string & host); + void SendProxyError(std::string & content); - uint8_t m_http_buff[http_buffer_size]; + uint8_t m_recv_chunk[8192]; + std::string m_recv_buf; // from client + std::string m_send_buf; // to upstream std::shared_ptr m_sock; - std::string m_request; //Data left to be sent - std::string m_url; //URL - std::string m_method; //Method - std::string m_version; //HTTP version - std::string m_address; //Address - std::string m_path; //Path - int m_port; //Port - state m_state;//Parsing state public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock) - { EnterState(GET_METHOD); } + I2PServiceHandler(parent), m_sock(sock) {} ~HTTPReqHandler() { Terminate(); } - void Handle () { AsyncSockRead(); } + void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() @@ -86,7 +83,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -102,210 +99,192 @@ namespace proxy { Done(shared_from_this()); } - /* All hope is lost beyond this point */ - //TODO: handle this apropriately - void HTTPReqHandler::HTTPRequestFailed(const char *message) + void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { + std::stringstream ss; + ss << "

Proxy error: " << title << "

\r\n"; + ss << "

" << description << "

\r\n"; + std::string content = ss.str(); + SendProxyError(content); + } + + void HTTPReqHandler::HostNotFound(std::string & host) { + std::stringstream ss; + ss << "

Proxy error: Host not found

\r\n" + << "

Remote host not found in router's addressbook

\r\n" + << "

You may try to find this host on jumpservices below:

\r\n" + << "
    \r\n"; + for (auto & js : jumpservices) { + ss << "
  • " << js.first << "
  • \r\n"; + } + ss << "
\r\n"; + std::string content = ss.str(); + SendProxyError(content); + } + + void HTTPReqHandler::SendProxyError(std::string & content) { i2p::http::HTTPRes res; res.code = 500; - res.add_header("Content-Type", "text/plain"); + res.add_header("Content-Type", "text/html; charset=UTF-8"); res.add_header("Connection", "close"); - res.body = message; - res.body += "\r\n"; + std::stringstream ss; + ss << "\r\n" << pageHead + << "" << content << "\r\n" + << "\r\n"; + res.body = ss.str(); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::RedirectToJumpService(std::string & host) + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { - i2p::http::HTTPRes res; - i2p::http::URL url; - - /* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ - i2p::config::GetOption("http.address", url.host); - i2p::config::GetOption("http.port", url.port); - url.path = "/"; - url.query = "page=jumpservices&address="; - url.query += host; + const char *param = "i2paddresshelper="; + std::size_t pos = url.query.find(param); + std::size_t len = std::strlen(param); + std::map params; - res.code = 302; /* redirect */ - res.add_header("Location", url.to_string().c_str()); + if (pos == std::string::npos) + return false; /* not found */ + if (!url.parse_query(params)) + return false; - std::string response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + std::string value = params["i2paddresshelper"]; + len += value.length(); + b64 = i2p::http::UrlDecode(value); + url.query.replace(pos, len, ""); + return true; } - void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { - m_state = nstate; + /* drop common headers */ + req.del_header("Referer"); + req.del_header("Via"); + req.del_header("Forwarded"); + /* drop proxy-disclosing headers */ + std::vector toErase; + for (auto it : req.headers) { + if (it.first.compare(0, 12, "X-Forwarded-") == 0) { + toErase.push_back(it.first); + } else if (it.first.compare(0, 6, "Proxy-") == 0) { + toErase.push_back(it.first); + } else { + /* allow */ + } + } + for (auto header : toErase) { + req.headers.erase(header); + } + /* replace headers */ + req.add_header("Connection", "close", true); /* keep-alive conns not supported yet */ + req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); /* privacy */ } - void HTTPReqHandler::ExtractRequest() + /** + * @brief Try to parse request from @a m_recv_buf + * If parsing success, rebuild request and store to @a m_send_buf + * with remaining data tail + * @return true on processed request or false if more data needed + */ + bool HTTPReqHandler::HandleRequest() { - LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); + i2p::http::HTTPReq req; i2p::http::URL url; - url.parse (m_url); - m_address = url.host; - m_port = url.port; - m_path = url.path; - if (url.query.length () > 0) m_path += "?" + url.query; - if (!m_port) m_port = 80; - LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); - } + std::string b64; + int req_len = 0; - bool HTTPReqHandler::ValidateHTTPRequest() - { - if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) - { - LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed("unsupported HTTP version"); - return false; - } - return true; - } + req_len = req.parse(m_recv_buf); - void HTTPReqHandler::HandleJumpServices() - { - static const char * helpermark1 = "?i2paddresshelper="; - static const char * helpermark2 = "&i2paddresshelper="; - size_t addressHelperPos1 = m_path.rfind (helpermark1); - size_t addressHelperPos2 = m_path.rfind (helpermark2); - size_t addressHelperPos; - if (addressHelperPos1 == std::string::npos) - { - if (addressHelperPos2 == std::string::npos) - return; //Not a jump service - else - addressHelperPos = addressHelperPos2; + if (req_len == 0) + return false; /* need more data */ + + if (req_len < 0) { + LogPrint(eLogError, "HTTPProxy: unable to parse request"); + GenericProxyError("Invalid request", "Proxy unable to parse your request"); + return true; /* parse error */ } - else - { - if (addressHelperPos2 == std::string::npos) - addressHelperPos = addressHelperPos1; - else if ( addressHelperPos1 > addressHelperPos2 ) - addressHelperPos = addressHelperPos1; - else - addressHelperPos = addressHelperPos2; + + /* parsing success, now let's look inside request */ + LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); + url.parse(req.uri); + + if (ExtractAddressHelper(url, b64)) { + i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host); + std::string full_url = url.to_string(); + std::stringstream ss; + ss << "Host " << url.host << " added to router's addressbook from helper. " + << "Click here to proceed."; + GenericProxyError("Addresshelper found", ss.str().c_str()); + return true; /* request processed */ } - auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1)); - base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded - LogPrint (eLogInfo, "HTTPProxy: jump service for ", m_address, ", inserting to address book"); - //TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/ - //TODO: we could redirect the user again to avoid dirtiness in the browser - i2p::client::context.GetAddressBook ().InsertAddress (m_address, base64); - m_path.erase(addressHelperPos); - } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) - { - ExtractRequest(); //TODO: parse earlier - if (!ValidateHTTPRequest()) return false; - HandleJumpServices(); + SanitizeHTTPRequest(req); + + std::string dest_host = url.host; + uint16_t dest_port = url.port; + /* always set port, even if missing in request */ + if (!dest_port) { + dest_port = (url.schema == "https") ? 443 : 80; + } + /* detect dest_host, set proper 'Host' header in upstream request */ + auto h = req.headers.find("Host"); + if (dest_host != "") { + /* absolute url, replace 'Host' header */ + std::string h = dest_host; + if (dest_port != 0 && dest_port != 80) + h += ":" + std::to_string(dest_port); + req.add_header("Host", h, true); + } else if (h != req.headers.end()) { + /* relative url and 'Host' header provided. transparent proxy mode? */ + i2p::http::URL u; + std::string t = "http://" + h->second; + u.parse(t); + dest_host = u.host; + dest_port = u.port; + } else { + /* relative url and missing 'Host' header */ + GenericProxyError("Invalid request", "Can't detect destination host from request"); + return true; + } + /* check dest_host really exists and inside I2P network */ i2p::data::IdentHash identHash; - if (str_rmatch(m_address, ".i2p")) - { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(m_address); - return false; + if (str_rmatch(dest_host, ".i2p")) { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { + HostNotFound(dest_host); + return true; /* request processed */ } + /* TODO: outproxy handler here */ + } else { + LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": not implemented yet"); + std::string message = "Host" + dest_host + "not inside I2P network, but outproxy support not implemented yet"; + GenericProxyError("Outproxy failure", message.c_str()); + return true; } - m_request = m_method; - m_request.push_back(' '); - m_request += m_path; - m_request.push_back(' '); - m_request += m_version; - m_request.push_back('\r'); - m_request.push_back('\n'); - m_request.append("Connection: close\r\n"); - // TODO: temporary shortcut. Must be implemented properly - uint8_t * eol = nullptr; - bool isEndOfHeader = false; - while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len))) - { - if (eol) - { - *eol = 0; eol++; - if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection - { - if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent - m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); - else - m_request.append ((const char *)http_buff); - m_request.append ("\r\n"); - } - isEndOfHeader = !http_buff[0]; - auto l = eol - http_buff; - http_buff = eol; - len -= l; - if (len > 0) // \r - { - http_buff++; - len--; - } - } - } - m_request.append(reinterpret_cast(http_buff),len); - return true; - } + /* make relative url */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); - bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) - { - while (len > 0) - { - //TODO: fallback to finding HOst: header if needed - switch (m_state) - { - case GET_METHOD: - switch (*http_buff) - { - case ' ': EnterState(GET_HOSTNAME); break; - default: m_method.push_back(*http_buff); break; - } - break; - case GET_HOSTNAME: - switch (*http_buff) - { - case ' ': EnterState(GET_HTTPV); break; - default: m_url.push_back(*http_buff); break; - } - break; - case GET_HTTPV: - switch (*http_buff) - { - case '\r': EnterState(GET_HTTPVNL); break; - default: m_version.push_back(*http_buff); break; - } - break; - case GET_HTTPVNL: - switch (*http_buff) - { - case '\n': EnterState(DONE); break; - default: - LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed("rejected invalid request"); - return false; - } - break; - default: - LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed("invalid parser state"); - return false; - } - http_buff++; - len--; - if (m_state == DONE) - return CreateHTTPRequest(http_buff,len); - } + /* drop original request from recv buffer */ + m_recv_buf.erase(0, req_len); + /* build new buffer from modified request and data from original request */ + m_send_buf = req.to_string(); + m_send_buf.append(m_recv_buf); + /* connect to destination */ + LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } + /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes"); + LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); @@ -313,17 +292,12 @@ namespace proxy { return; } - if (HandleData(m_http_buff, len)) - { - if (m_state == DONE) - { - LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_address, m_port); - } - else - AsyncSockRead(); + m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); + if (HandleRequest()) { + m_recv_buf.clear(); + return; } + AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) @@ -337,15 +311,15 @@ namespace proxy { { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed("error when creating the stream, check logs"); + GenericProxyError("Host is down", "Can't create connection to requested host, it may be down"); return; } if (Kill()) return; - LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); + LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); Done (shared_from_this()); } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index cfcf4261..566ad53e 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -71,24 +71,18 @@ namespace http { const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; - const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; const char HTTP_PAGE_COMMANDS[] = "commands"; - const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; - const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + const char HTTP_PAGE_LEASESETS[] = "leasesets"; + const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; + const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; - const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; - std::map jumpservices = { - { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, - { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, - }; - void ShowUptime (std::stringstream& s, int seconds) { int num; @@ -147,11 +141,11 @@ namespace http { " Main page
\r\n
\r\n" " Router commands
\r\n" " Local destinations
\r\n" + " Lease Sets
\r\n" " Tunnels
\r\n" " Transit tunnels
\r\n" " Transports
\r\n" " I2P tunnels
\r\n" - " Jump services
\r\n" " SAM sessions
\r\n" "\r\n" "
"; @@ -247,20 +241,6 @@ namespace http { s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } - void ShowJumpServices (std::stringstream& s, const std::string& address) - { - s << "
"; - s << ""; - s << ""; - s << ""; - s << "

\r\n"; - s << "Jump services for " << address << "\r\n\r\n"; - } - void ShowLocalDestinations (std::stringstream& s) { s << "Local Destinations:
\r\n
\r\n"; @@ -349,6 +329,57 @@ namespace http { } } + void ShowLeasesSets(std::stringstream& s) + { + s << "
LeaseSets

"; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); + // begin lease set entry + s << "
"; + // invalid ? + if (!ls.IsValid()) + s << "
!! Invalid !!
"; + // ident + s << "
" << dest.ToBase32() << "
"; + // LeaseSet time + s << "
expires: " << ls.GetExpirationTime() << "
"; + // get non expired leases + auto leases = ls.GetNonExpiredLeases(); + // show non expired leases + s << "
Non Expired Leases: " << leases.size() << "
"; + // for each lease + s << "
"; + for ( auto & l : leases ) + { + // begin lease + s << "
"; + // gateway + s << "
Gateway: " << l->tunnelGateway.ToBase64() << "
"; + // tunnel id + s << "
TunnelID: " << l->tunnelID << "
"; + // end date + s << "
EndDate: " << l->endDate << "
"; + // end lease + s << "
"; + } + // end for each lease + s << "
"; + // end lease set entry + s << "
"; + // linebreak + s << "
"; + } + ); + // end for each lease set + } + void ShowTunnels (std::stringstream& s) { s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; @@ -374,10 +405,10 @@ namespace http { s << " Run peer test
\r\n"; //s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Stop accepting tunnels
\r\n"; + s << " Decline transit tunnels
\r\n"; else - s << " Start accepting tunnels
\r\n"; -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) + s << " Accept transit tunnels
\r\n"; +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; @@ -649,8 +680,6 @@ namespace http { ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) ShowCommands (s); - else if (page == HTTP_PAGE_JUMPSERVICES) - ShowJumpServices (s, params["address"]); else if (page == HTTP_PAGE_TRANSIT_TUNNELS) ShowTransitTunnels (s); else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) @@ -663,6 +692,8 @@ namespace http { ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); + else if (page == HTTP_PAGE_LEASESETS) + ShowLeasesSets(s); else { res.code = 400; ShowError(s, "Unknown page: " + page); @@ -684,18 +715,18 @@ namespace http { i2p::transport::transports.PeerTest (); else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) i2p::client::context.ReloadConfig (); - else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) + else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); - else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) + else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 10*60; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 0; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 1b2d0317..92ab8281 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -165,9 +165,10 @@ namespace i2p buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags - htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID - buf += 5; + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags + buf ++; + htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID + buf += 4; // excluded htobe16buf (buf, cnt); @@ -182,7 +183,7 @@ namespace i2p } // encryption memcpy (buf, replyKey, 32); - buf[32] = 1; // 1 tag + buf[32] = uint8_t( 1 ); // 1 tag memcpy (buf + 33, replyTag, 32); buf += 65; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 1ade55ed..76871114 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -90,7 +90,7 @@ namespace i2p // DatabaseLookup flags const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; - const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02; + const uint8_t DATABASE_LOOKUP_ENCRYPTION_FLAG = 0x02; const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C; const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 diff --git a/Makefile b/Makefile index 22f016ea..147bedd4 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ else # win32 mingw endif ifeq ($(USE_MESHNET),yes) - CXXFLAGS += -DMESHNET + NEEDED_CXXFLAGS += -DMESHNET endif all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 9a2b6687..d2c03857 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -713,8 +713,16 @@ namespace transport if (m_IsTerminated) return; if (m_IsSending) { - for (auto it: msgs) - m_SendQueue.push_back (it); + if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE) + { + for (auto it: msgs) + m_SendQueue.push_back (it); + } + else + { + LogPrint (eLogWarning, "NTCP: outgoing messages queue size exceeds ", NTCP_MAX_OUTGOING_QUEUE_SIZE); + Terminate (); + } } else Send (msgs); diff --git a/NTCPSession.h b/NTCPSession.h index 2a60f1dc..59b6bb58 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -40,6 +40,7 @@ namespace transport const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second const int NTCP_CLOCK_SKEW = 60; // in seconds + const int NTCP_MAX_OUTGOING_QUEUE_SIZE = 200; // how many messages we can queue up class NTCPServer; class NTCPSession: public TransportSession, public std::enable_shared_from_this diff --git a/NetDb.cpp b/NetDb.cpp index f89617be..f5b51064 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -213,6 +213,7 @@ namespace data bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from) { + std::unique_lock lock(m_LeaseSetsMutex); bool updated = false; if (!from) // unsolicited LS must be received directly { @@ -264,6 +265,7 @@ namespace data std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) return it->second; @@ -318,6 +320,13 @@ namespace data return true; } + void NetDb::VisitLeaseSets(LeaseSetVisitor v) + { + std::unique_lock lock(m_LeaseSetsMutex); + for ( auto & entry : m_LeaseSets) + v(entry.first, entry.second); + } + void NetDb::Load () { // make sure we cleanup netDb from previous attempts @@ -623,17 +632,18 @@ namespace data char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; - uint8_t flag = buf[64]; IdentHash replyIdent(buf + 32); - + uint8_t flag = buf[64]; + + LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { - replyTunnelID = bufbe32toh (buf + 65); + replyTunnelID = bufbe32toh (excluded); excluded += 4; } uint16_t numExcluded = bufbe16toh (excluded); @@ -641,7 +651,7 @@ namespace data if (numExcluded > 512) { LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512"); - numExcluded = 0; // TODO: + return; } std::shared_ptr replyMsg; @@ -714,35 +724,39 @@ namespace data } if (!found) { - std::set excludedRouters; + std::set excludedRouters; + const uint8_t * exclude_ident = excluded; for (int i = 0; i < numExcluded; i++) { - excludedRouters.insert (excluded); - excluded += 32; + excludedRouters.insert (exclude_ident); + exclude_ident += 32; } closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); if (!numExcluded) // save if no excluded m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ()); } replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); - } + } } - + excluded += numExcluded * 32; if (replyMsg) { if (replyTunnelID) { // encryption might be used though tunnel only - if (flag & DATABASE_LOOKUP_ENCYPTION_FLAG) // encrypted reply requested + if (flag & DATABASE_LOOKUP_ENCRYPTION_FLAG) // encrypted reply requested { const uint8_t * sessionKey = excluded; - uint8_t numTags = sessionKey[32]; - if (numTags > 0) + const uint8_t numTags = excluded[32]; + if (numTags) { - const uint8_t * sessionTag = sessionKey + 33; // take first tag + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); + if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); } + else + LogPrint(eLogWarning, "NetDb: encrypted reply requested but no tags provided"); } auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; diff --git a/NetDb.h b/NetDb.h index 3b54ae4c..43b069b9 100644 --- a/NetDb.h +++ b/NetDb.h @@ -31,12 +31,10 @@ namespace data const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours - -#ifdef MESHNET - const int NETDB_PUBLISH_INTERVAL = 60; -#else const int NETDB_PUBLISH_INTERVAL = 60*40; -#endif + + /** function for visiting a leaseset stored in a floodfill */ + typedef std::function)> LeaseSetVisitor; class NetDb { @@ -85,7 +83,10 @@ namespace data int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; - + + /** visit all lease sets we currently store */ + void VisitLeaseSets(LeaseSetVisitor v); + private: void Load (); @@ -103,6 +104,7 @@ namespace data private: + mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; std::map > m_RouterInfos; diff --git a/Reseed.cpp b/Reseed.cpp index 096035da..66609414 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -14,7 +14,7 @@ #include "Log.h" #include "Identity.h" #include "NetDb.h" -#include "util.h" +#include "HTTP.h" namespace i2p { @@ -372,13 +372,19 @@ namespace data std::string Reseeder::HttpsRequest (const std::string& address) { - i2p::util::http::url u(address); - if (u.port_ == 80) u.port_ = 443; + i2p::http::URL url; + if (!url.parse(address)) { + LogPrint(eLogError, "Reseed: failed to parse url: ", address); + return ""; + } + url.schema = "https"; + if (!url.port) + url.port = 443; boost::asio::io_service service; boost::system::error_code ecode; - auto it = boost::asio::ip::tcp::resolver(service).resolve ( - boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode); + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); if (!ecode) { boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); @@ -390,32 +396,52 @@ namespace data s.handshake (boost::asio::ssl::stream_base::client, ecode); if (!ecode) { - LogPrint (eLogInfo, "Reseed: Connected to ", u.host_, ":", u.port_); - // send request - std::stringstream ss; - ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_ - << "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n"; - s.write_some (boost::asio::buffer (ss.str ())); + LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); + i2p::http::HTTPReq req; + req.uri = url.to_string(); + req.add_header("User-Agent", "Wget/1.11.4"); + req.add_header("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); // read response std::stringstream rs; - char response[1024]; size_t l = 0; - do - { - l = s.read_some (boost::asio::buffer (response, 1024), ecode); - if (l) rs.write (response, l); - } - while (!ecode && l); + char recv_buf[1024]; size_t l = 0; + do { + l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); + if (l) rs.write (recv_buf, l); + } while (!ecode && l); // process response - return i2p::util::http::GetHttpContent (rs); + std::string data = rs.str(); + i2p::http::HTTPRes res; + int len = res.parse(data); + if (len <= 0) { + LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host); + return ""; + } + if (res.code != 200) { + LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code); + return ""; + } + data.erase(0, len); /* drop http headers from response */ + LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host); + if (res.is_chunked()) { + std::stringstream in(data), out; + if (!i2p::http::MergeChunkedResponse(in, out)) { + LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host); + return ""; + } + LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host); + data = out.str(); + } + return data; } else LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); } else - LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); } else - LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ()); return ""; } } diff --git a/RouterContext.cpp b/RouterContext.cpp index 768750bb..737a92fc 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -49,10 +49,19 @@ namespace i2p uint16_t port; i2p::config::GetOption("port", port); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - std::string host; i2p::config::GetOption("host", host); - if (i2p::config::IsDefault("host")) - host = "127.0.0.1"; // replace default address with safe value - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool nat; i2p::config::GetOption("nat", nat); + std::string ifname; i2p::config::GetOption("ifname", ifname); + std::string host = ipv6 ? "::" : "127.0.0.1"; + if (nat) { + if (!i2p::config::IsDefault("host")) + i2p::config::GetOption("host", host); + } else if (!ifname.empty()) { + /* bind to interface, we have no NAT so set external address too */ + host = i2p::util::net::GetInterfaceAddress(ifname, ipv6).to_string(); + } + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC @@ -224,11 +233,12 @@ namespace i2p m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B // remove NTCP address auto& addresses = m_RouterInfo.GetAddresses (); - for (size_t i = 0; i < addresses.size (); i++) + for (auto it = addresses.begin (); it != addresses.end (); it++) { - if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP) + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*it)->host.is_v4 ()) { - addresses.erase (addresses.begin () + i); + addresses.erase (it); break; } } @@ -253,12 +263,13 @@ namespace i2p // insert NTCP back auto& addresses = m_RouterInfo.GetAddresses (); - for (size_t i = 0; i < addresses.size (); i++) + for (auto addr : addresses) { - if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU) + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && + addr->host.is_v4 ()) { // insert NTCP address with host/port from SSU - m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port); + m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); break; } } diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 3462d7d8..f497c30e 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -15,10 +15,16 @@ namespace i2p { namespace data { + RouterInfo::RouterInfo (): m_Buffer (nullptr) + { + m_Addresses = std::make_shared(); // create empty list + } + RouterInfo::RouterInfo (const std::string& fullPath): m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { + m_Addresses = std::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; ReadFromFile (); } @@ -26,6 +32,7 @@ namespace data RouterInfo::RouterInfo (const uint8_t * buf, int len): m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { + m_Addresses = std::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; memcpy (m_Buffer, buf, len); m_BufferLen = len; @@ -48,7 +55,7 @@ namespace data m_IsUnreachable = false; m_SupportedTransports = 0; m_Caps = 0; - m_Addresses.clear (); + // don't clean up m_Addresses, it will be replaced in ReadFromStream m_Properties.clear (); // copy buffer if (!m_Buffer) @@ -144,6 +151,7 @@ namespace data s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses + auto addresses = std::make_shared(); uint8_t numAddresses; s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; bool introducers = false; @@ -234,10 +242,11 @@ namespace data } if (isValidAddress) { - m_Addresses.push_back(std::make_shared
(address)); + addresses->push_back(std::make_shared
(address)); m_SupportedTransports |= supportedTransports; } } + m_Addresses = addresses; // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; @@ -288,7 +297,7 @@ namespace data if (!s) return; } - if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers)) + if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers)) SetUnreachable (true); } @@ -366,9 +375,9 @@ namespace data s.write ((char *)&ts, sizeof (ts)); // addresses - uint8_t numAddresses = m_Addresses.size (); + uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (auto addr : m_Addresses) + for (auto addr : *m_Addresses) { Address& address = *addr; s.write ((char *)&address.cost, sizeof (address.cost)); @@ -561,9 +570,9 @@ namespace data addr->cost = 2; addr->date = 0; addr->mtu = 0; - for (auto it: m_Addresses) // don't insert same address twice + for (auto it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses.push_back(addr); + m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; } @@ -577,9 +586,9 @@ namespace data addr->date = 0; addr->mtu = mtu; memcpy (addr->key, key, 32); - for (auto it: m_Addresses) // don't insert same address twice + for (auto it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses.push_back(addr); + m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Caps |= eSSUTesting; m_Caps |= eSSUIntroducer; @@ -587,7 +596,7 @@ namespace data bool RouterInfo::AddIntroducer (const Introducer& introducer) { - for (auto addr : m_Addresses) + for (auto addr : *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { @@ -602,7 +611,7 @@ namespace data bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { - for (auto addr: m_Addresses) + for (auto addr: *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { @@ -691,28 +700,14 @@ namespace data { if (IsV6 ()) { - // NTCP - m_SupportedTransports &= ~eNTCPV6; - for (size_t i = 0; i < m_Addresses.size (); i++) - { - if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && - m_Addresses[i]->host.is_v6 ()) - { - m_Addresses.erase (m_Addresses.begin () + i); - break; - } - } - - // SSU - m_SupportedTransports &= ~eSSUV6; - for (size_t i = 0; i < m_Addresses.size (); i++) + m_SupportedTransports &= ~(eNTCPV6 | eSSUV6); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && - m_Addresses[i]->host.is_v6 ()) - { - m_Addresses.erase (m_Addresses.begin () + i); - break; - } + auto addr = *it; + if (addr->host.is_v6 ()) + it = m_Addresses->erase (it); + else + it++; } } } @@ -721,28 +716,14 @@ namespace data { if (IsV4 ()) { - // NTCP - m_SupportedTransports &= ~eNTCPV4; - for (size_t i = 0; i < m_Addresses.size (); i++) - { - if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && - m_Addresses[i]->host.is_v4 ()) - { - m_Addresses.erase (m_Addresses.begin () + i); - break; - } - } - - // SSU - m_SupportedTransports &= ~eSSUV4; - for (size_t i = 0; i < m_Addresses.size (); i++) + m_SupportedTransports &= ~(eNTCPV4 | eSSUV4); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && - m_Addresses[i]->host.is_v4 ()) - { - m_Addresses.erase (m_Addresses.begin () + i); - break; - } + auto addr = *it; + if (addr->host.is_v4 ()) + it = m_Addresses->erase (it); + else + it++; } } } @@ -770,7 +751,8 @@ namespace data std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { - for (auto address : m_Addresses) + auto addresses = m_Addresses; + for (auto address : *addresses) { if (address->transportStyle == s) { diff --git a/RouterInfo.h b/RouterInfo.h index 8c8af691..8c99d245 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "Identity.h" @@ -105,9 +106,10 @@ namespace data return !(*this == other); } }; - + typedef std::list > Addresses; + + RouterInfo (); RouterInfo (const std::string& fullPath); - RouterInfo (): m_Buffer (nullptr) { }; RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (const uint8_t * buf, int len); @@ -117,7 +119,7 @@ namespace data void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; - std::vector >& GetAddresses () { return m_Addresses; }; + Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCPAddress (bool v4only = true) const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; @@ -199,7 +201,7 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; - std::vector > m_Addresses; + std::shared_ptr m_Addresses; std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; diff --git a/Streaming.cpp b/Streaming.cpp index 74046ef7..3806d97d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -201,7 +201,7 @@ namespace stream memset (const_cast(optionData), 0, signatureLen); if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) { - LogPrint (eLogError, "Streaming: Signature verification failed"); + LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); Close (); flags |= PACKET_FLAG_CLOSE; } @@ -222,6 +222,7 @@ namespace stream if (flags & PACKET_FLAG_RESET) { + LogPrint (eLogDebug, "Streaming: closing stream sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ": reset flag received in packet #", receivedSeqn); m_Status = eStreamStatusReset; Close (); } @@ -495,6 +496,7 @@ namespace stream void Stream::Close () { + LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); switch (m_Status) { case eStreamStatusOpen: @@ -668,7 +670,7 @@ namespace stream // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { - LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; @@ -703,7 +705,7 @@ namespace stream case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); break; case 3: // pick another outbound tunnel @@ -725,7 +727,7 @@ namespace stream { if (m_LastReceivedSequenceNumber < 0) { - LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: SYN has not been received after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; diff --git a/Transports.cpp b/Transports.cpp index b4130dfd..a54455cc 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -93,7 +93,7 @@ namespace transport Transports transports; Transports::Transports (): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service), + m_IsOnline (true), m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) @@ -114,7 +114,7 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address : addresses) { - if (!m_NTCPServer && enableNTCP) + if (m_NTCPServer == nullptr && enableNTCP) { m_NTCPServer = new NTCPServer (); m_NTCPServer->Start (); @@ -129,7 +129,7 @@ namespace transport if (address->transportStyle == RouterInfo::eTransportSSU) { - if (!m_SSUServer && enableSSU) + if (m_SSUServer == nullptr && enableSSU) { if (address->host.is_v4()) m_SSUServer = new SSUServer (address->port); @@ -264,8 +264,17 @@ namespace transport it->second.sessions.front ()->SendI2NPMessages (msgs); else { - for (auto it1: msgs) - it->second.delayedMessages.push_back (it1); + if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) + { + for (auto it1: msgs) + it->second.delayedMessages.push_back (it1); + } + else + { + LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); + } } } @@ -337,7 +346,7 @@ namespace transport } LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); peer.Done (); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -369,7 +378,7 @@ namespace transport else { LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } @@ -413,7 +422,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -455,7 +464,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -590,7 +599,7 @@ namespace transport ConnectToPeer (ident, it->second); else { - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } diff --git a/Transports.h b/Transports.h index 9b231802..708c55e2 100644 --- a/Transports.h +++ b/Transports.h @@ -66,6 +66,7 @@ namespace transport }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds + const int MAX_NUM_DELAYED_MESSAGES = 50; class Transports { public: @@ -79,6 +80,9 @@ namespace transport bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } bool IsBoundSSU() const { return m_SSUServer != nullptr; } + bool IsOnline() const { return m_IsOnline; }; + void SetOnline (bool online) { m_IsOnline = online; }; + boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetNextDHKeysPair (); void ReuseDHKeysPair (std::shared_ptr pair); @@ -132,7 +136,7 @@ namespace transport private: - bool m_IsRunning; + bool m_IsOnline, m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; diff --git a/UPnP.cpp b/UPnP.cpp index 477342b3..41b49e00 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -13,6 +13,7 @@ #include "NetDb.h" #include "util.h" #include "RouterInfo.h" +#include "Config.h" #include #include @@ -21,47 +22,54 @@ namespace i2p { namespace transport { - UPnP::UPnP () : m_Thread (nullptr) + UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) { } void UPnP::Stop () { - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } + if (m_IsRunning) + { + LogPrint(eLogInfo, "UPnP: stopping"); + m_IsRunning = false; + m_Timer.cancel (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + m_Thread.reset (nullptr); + } + CloseMapping (); + Close (); + } } void UPnP::Start() { - m_Thread = new std::thread (std::bind (&UPnP::Run, this)); + m_IsRunning = true; + LogPrint(eLogInfo, "UPnP: starting"); + m_Service.post (std::bind (&UPnP::Discover, this)); + m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); } UPnP::~UPnP () { + Stop (); } void UPnP::Run () { - const std::vector > a = context.GetRouterInfo().GetAddresses(); - for (auto address : a) - { - if (!address->host.is_v6 ()) - { - Discover (); - if (address->transportStyle == data::RouterInfo::eTransportSSU ) - { - TryPortMapping (I2P_UPNP_UDP, address->port); - } - else if (address->transportStyle == data::RouterInfo::eTransportNTCP ) - { - TryPortMapping (I2P_UPNP_TCP, address->port); - } - } - } + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); + } + } } void UPnP::Discover () @@ -80,81 +88,82 @@ namespace transport r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) { - LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress () returned ", r); + LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress() returned ", r); return; } else { - if (m_externalIPAddress[0]) + if (!m_externalIPAddress[0]) { - LogPrint (eLogInfo, "UPnP: ExternalIPAddress = ", m_externalIPAddress); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); - return; - } - else - { - LogPrint (eLogError, "UPnP: GetExternalIPAddress failed."); + LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed."); return; } } } + else + { + LogPrint (eLogError, "UPnP: GetValidIGD() failed."); + return; + } + + // UPnP discovered + LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); + i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); + // port mapping + PortMapping (); } - void UPnP::TryPortMapping (int type, int port) - { - std::string strType, strPort (std::to_string (port)); - switch (type) + void UPnP::PortMapping () + { + auto a = context.GetRouterInfo().GetAddresses(); + for (auto address : a) { - case I2P_UPNP_TCP: - strType = "TCP"; - break; - case I2P_UPNP_UDP: - default: - strType = "UDP"; + if (!address->host.is_v6 ()) + TryPortMapping (address); + } + m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes + m_Timer.async_wait ([this](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + PortMapping (); + }); + + } + + void UPnP::CloseMapping () + { + auto a = context.GetRouterInfo().GetAddresses(); + for (auto address : a) + { + if (!address->host.is_v6 ()) + CloseMapping (address); } + } + + void UPnP::TryPortMapping (std::shared_ptr address) + { + std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r; - std::string strDesc = "I2Pd"; - try { - for (;;) { - r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); - if (r!=UPNPCOMMAND_SUCCESS) - { - LogPrint (eLogError, "UPnP: AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); - return; - } - else - { - LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", strPort.c_str() ,")"); - return; - } - std::this_thread::sleep_for(std::chrono::minutes(20)); // c++11 - //boost::this_thread::sleep_for(); // pre c++11 - //sleep(20*60); // non-portable - } + std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); + r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); + if (r!=UPNPCOMMAND_SUCCESS) + { + LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") failed with code ", r); + return; } - catch (boost::thread_interrupted) + else { - CloseMapping(type, port); - Close(); - throw; + LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")"); + return; } } - void UPnP::CloseMapping (int type, int port) + void UPnP::CloseMapping (std::shared_ptr address) { - std::string strType, strPort (std::to_string (port)); - switch (type) - { - case I2P_UPNP_TCP: - strType = "TCP"; - break; - case I2P_UPNP_UDP: - default: - strType = "UDP"; - } + std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r = 0; r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); - LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r, "\n"); + LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r); } void UPnP::Close () @@ -164,9 +173,23 @@ namespace transport FreeUPNPUrls (&m_upnpUrls); } + std::string UPnP::GetProto (std::shared_ptr address) + { + switch (address->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP: + return "TCP"; + break; + case i2p::data::RouterInfo::eTransportSSU: + default: + return "UDP"; + } + } } } - - -#endif - +#else /* USE_UPNP */ +namespace i2p { +namespace transport { +} +} +#endif /* USE_UPNP */ diff --git a/UPnP.h b/UPnP.h index 0a000177..4013b3df 100644 --- a/UPnP.h +++ b/UPnP.h @@ -4,6 +4,7 @@ #ifdef USE_UPNP #include #include +#include #include #include @@ -14,9 +15,6 @@ #include "util.h" -#define I2P_UPNP_TCP 1 -#define I2P_UPNP_UDP 2 - namespace i2p { namespace transport @@ -32,13 +30,23 @@ namespace transport void Start (); void Stop (); - void Discover (); - void TryPortMapping (int type, int port); - void CloseMapping (int type, int port); private: + + void Discover (); + void PortMapping (); + void TryPortMapping (std::shared_ptr address); + void CloseMapping (); + void CloseMapping (std::shared_ptr address); + void Run (); + std::string GetProto (std::shared_ptr address); - std::thread * m_Thread; + private: + + bool m_IsRunning; + std::unique_ptr m_Thread; + boost::asio::io_service m_Service; + boost::asio::deadline_timer m_Timer; struct UPNPUrls m_upnpUrls; struct IGDdatas m_upnpData; @@ -52,5 +60,18 @@ namespace transport } } +#else // USE_UPNP +namespace i2p { +namespace transport { + /* class stub */ + class UPnP { + public: + UPnP () {}; + ~UPnP () {}; + void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } + void Stop () {}; + }; +} +} #endif // USE_UPNP #endif // __UPNP_H__ diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..1ecaafbe --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,7 @@ +gen +tests +.idea +local.properties +build.sh +bin +log* \ No newline at end of file diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100755 index 00000000..b6cc6f26 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 00000000..23e9f065 --- /dev/null +++ b/android/build.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100755 index 00000000..90a679b2 --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,113 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := i2pd +LOCAL_CPP_FEATURES := rtti exceptions +LOCAL_C_INCLUDES += $(IFADDRS_PATH) ../.. +LOCAL_STATIC_LIBRARIES := \ + boost_system-gcc-mt-1_53 \ + boost_date_time-gcc-mt-1_53 \ + boost_filesystem-gcc-mt-1_53 \ + boost_program_options-gcc-mt-1_53 \ + crypto ssl \ + miniupnpc +LOCAL_LDLIBS := -lz + +LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ + $(IFADDRS_PATH)/ifaddrs.c \ + ../../HTTPServer.cpp ../../I2PControl.cpp ../../Daemon.cpp ../../Config.cpp \ + ../../AddressBook.cpp \ + ../../api.cpp \ + ../../Base.cpp \ + ../../BOB.cpp \ + ../../ClientContext.cpp \ + ../../Crypto.cpp \ + ../../Datagram.cpp \ + ../../Destination.cpp \ + ../../Family.cpp \ + ../../FS.cpp \ + ../../Garlic.cpp \ + ../../Gzip.cpp \ + ../../HTTP.cpp \ + ../../HTTPProxy.cpp \ + ../../I2CP.cpp \ + ../../I2NPProtocol.cpp \ + ../../I2PEndian.cpp \ + ../../I2PService.cpp \ + ../../I2PTunnel.cpp \ + ../../Identity.cpp \ + ../../LeaseSet.cpp \ + ../../Log.cpp \ + ../../NetDb.cpp \ + ../../NetDbRequests.cpp \ + ../../NTCPSession.cpp \ + ../../Profiling.cpp \ + ../../Reseed.cpp \ + ../../RouterContext.cpp \ + ../../RouterInfo.cpp \ + ../../SAM.cpp \ + ../../Signature.cpp \ + ../../SOCKS.cpp \ + ../../SSU.cpp \ + ../../SSUData.cpp \ + ../../SSUSession.cpp \ + ../../Streaming.cpp \ + ../../TransitTunnel.cpp \ + ../../Transports.cpp \ + ../../Tunnel.cpp \ + ../../TunnelEndpoint.cpp \ + ../../TunnelGateway.cpp \ + ../../TunnelPool.cpp \ + ../../util.cpp \ + ../../i2pd.cpp ../../UPnP.cpp + +include $(BUILD_SHARED_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_system-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_system-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_date_time-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_filesystem-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_program_options-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +LOCAL_STATIC_LIBRARIES := crypto +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := miniupnpc +LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnp-2.0/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a +LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnp-2.0/include +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100755 index 00000000..e8a51add --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,32 @@ +#APP_ABI := all +#APP_ABI := armeabi-v7a x86 +#APP_ABI := x86 +APP_ABI := armeabi-v7a +#can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there. +APP_PLATFORM := android-9 + +# http://stackoverflow.com/a/21386866/529442 http://stackoverflow.com/a/15616255/529442 to enable c++11 support in Eclipse +NDK_TOOLCHAIN_VERSION := 4.9 +# APP_STL := stlport_shared --> does not seem to contain C++11 features +APP_STL := gnustl_shared + +# Enable c++11 extentions in source code +APP_CPPFLAGS += -std=c++11 + +APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +APP_CPPFLAGS += -DANDROID_ARM7A +endif + +APP_OPTIM := debug + +# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/android-ifaddrs.git +# change to your own +I2PD_LIBS_PATH=/path/to/libraries +BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt +OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp new file mode 100644 index 00000000..038a07fa --- /dev/null +++ b/android/jni/DaemonAndroid.cpp @@ -0,0 +1,194 @@ +#include "DaemonAndroid.h" +#include "../../Daemon.h" +#include +#include +#include +#include +//#include "mainwindow.h" + +namespace i2p +{ +namespace android +{ +/* Worker::Worker (DaemonAndroidImpl& daemon): + m_Daemon (daemon) + { + } + + void Worker::startDaemon() + { + Log.d(TAG"Performing daemon start..."); + m_Daemon.start(); + Log.d(TAG"Daemon started."); + emit resultReady(); + } + void Worker::restartDaemon() + { + Log.d(TAG"Performing daemon restart..."); + m_Daemon.restart(); + Log.d(TAG"Daemon restarted."); + emit resultReady(); + } + void Worker::stopDaemon() { + Log.d(TAG"Performing daemon stop..."); + m_Daemon.stop(); + Log.d(TAG"Daemon stopped."); + emit resultReady(); + } + + Controller::Controller(DaemonAndroidImpl& daemon): + m_Daemon (daemon) + { + Worker *worker = new Worker (m_Daemon); + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); + connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); + connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); + connect(worker, &Worker::resultReady, this, &Controller::handleResults); + workerThread.start(); + } + Controller::~Controller() + { + Log.d(TAG"Closing and waiting for daemon worker thread..."); + workerThread.quit(); + workerThread.wait(); + Log.d(TAG"Waiting for daemon worker thread finished."); + if(m_Daemon.isRunning()) + { + Log.d(TAG"Stopping the daemon..."); + m_Daemon.stop(); + Log.d(TAG"Stopped the daemon."); + } + } +*/ + DaemonAndroidImpl::DaemonAndroidImpl () + //: + /*mutex(nullptr), */ + //m_IsRunning(false), + //m_RunningChangedCallback(nullptr) + { + } + + DaemonAndroidImpl::~DaemonAndroidImpl () + { + //delete mutex; + } + + bool DaemonAndroidImpl::init(int argc, char* argv[]) + { + //mutex=new QMutex(QMutex::Recursive); + //setRunningCallback(0); + //m_IsRunning=false; + return Daemon.init(argc,argv); + } + + void DaemonAndroidImpl::start() + { + //QMutexLocker locker(mutex); + //setRunning(true); + Daemon.start(); + } + + void DaemonAndroidImpl::stop() + { + //QMutexLocker locker(mutex); + Daemon.stop(); + //setRunning(false); + } + + void DaemonAndroidImpl::restart() + { + //QMutexLocker locker(mutex); + stop(); + start(); + } + /* + void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) + { + m_RunningChangedCallback = cb; + } + + bool DaemonAndroidImpl::isRunning() + { + return m_IsRunning; + } + + void DaemonAndroidImpl::setRunning(bool newValue) + { + bool oldValue = m_IsRunning; + if(oldValue!=newValue) + { + m_IsRunning = newValue; + if(m_RunningChangedCallback) + m_RunningChangedCallback(); + } + } +*/ + static DaemonAndroidImpl daemon; + static char* argv[1]={strdup("tmp")}; + /** + * returns error details if failed + * returns "ok" if daemon initialized and started okay + */ + std::string start(/*int argc, char* argv[]*/) + { + try + { + //int result; + + { + //Log.d(TAG"Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(1,argv); + if(!daemonInitSuccess) + { + //QMessageBox::critical(0, "Error", "Daemon init failed"); + return "Daemon init failed"; + } + //Log.d(TAG"Initialised, creating the main window..."); + //MainWindow w; + //Log.d(TAG"Before main window.show()..."); + //w.show (); + + { + //i2p::qt::Controller daemonQtController(daemon); + //Log.d(TAG"Starting the daemon..."); + //emit daemonQtController.startDaemon(); + //daemon.start (); + //Log.d(TAG"Starting GUI event loop..."); + //result = app.exec(); + //daemon.stop (); + daemon.start(); + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + //Log.d(TAG"Exiting the application"); + //return result; + } + catch (boost::exception& ex) + { + std::stringstream ss; + ss << boost::diagnostic_information(ex); + return ss.str(); + } + catch (std::exception& ex) + { + std::stringstream ss; + ss << ex.what(); + return ss.str(); + } + catch(...) + { + return "unknown exception"; + } + return "ok"; + } + + void stop() + { + daemon.stop(); + } +} +} + diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h new file mode 100644 index 00000000..9cc8219b --- /dev/null +++ b/android/jni/DaemonAndroid.h @@ -0,0 +1,87 @@ +#ifndef DAEMON_ANDROID_H +#define DAEMON_ANDROID_H + +#include + +namespace i2p +{ +namespace android +{ + class DaemonAndroidImpl + { + public: + + DaemonAndroidImpl (); + ~DaemonAndroidImpl (); + + //typedef void (*runningChangedCallback)(); + + /** + * @return success + */ + bool init(int argc, char* argv[]); + void start(); + void stop(); + void restart(); + //void setRunningCallback(runningChangedCallback cb); + //bool isRunning(); + private: + //void setRunning(bool running); + private: + //QMutex* mutex; + //bool m_IsRunning; + //runningChangedCallback m_RunningChangedCallback; + }; + + /** + * returns "ok" if daemon init failed + * returns errinfo if daemon initialized and started okay + */ + std::string start(); + + // stops the daemon + void stop(); + + /* + class Worker : public QObject + { + Q_OBJECT + public: + + Worker (DaemonAndroidImpl& daemon); + + private: + + DaemonAndroidImpl& m_Daemon; + + public slots: + void startDaemon(); + void restartDaemon(); + void stopDaemon(); + + signals: + void resultReady(); + }; + + class Controller : public QObject + { + Q_OBJECT + QThread workerThread; + public: + Controller(DaemonAndroidImpl& daemon); + ~Controller(); + private: + DaemonAndroidImpl& m_Daemon; + + public slots: + void handleResults(){} + signals: + void startDaemon(); + void stopDaemon(); + void restartDaemon(); + }; + */ +} +} + +#endif // DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp new file mode 100755 index 00000000..201b668e --- /dev/null +++ b/android/jni/i2pd_android.cpp @@ -0,0 +1,66 @@ + +//#include +#include +#include "org_purplei2p_i2pd_I2PD_JNI.h" +#include "DaemonAndroid.h" +#include "../../RouterContext.h" +#include "../../Transports.h" + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith + (JNIEnv * env, jclass clazz) { +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif +#elif defined(__i386__) + #define ABI "x86" +#elif defined(__x86_64__) + #define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ + #define ABI "mips64" +#elif defined(__mips__) + #define ABI "mips" +#elif defined(__aarch64__) + #define ABI "arm64-v8a" +#else + #define ABI "unknown" +#endif + + return env->NewStringUTF(ABI); +} + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon + (JNIEnv * env, jclass clazz) { + return env->NewStringUTF(i2p::android::start().c_str()); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon + (JNIEnv * env, jclass clazz) { + i2p::android::stop(); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels + (JNIEnv * env, jclass clazz) { + i2p::context.SetAcceptsTunnels (false); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged + (JNIEnv * env, jclass clazz, jboolean isConnected) +{ + bool isConnectedBool = (bool) isConnected; + i2p::transport::transports.SetOnline (isConnectedBool); +} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h new file mode 100644 index 00000000..04923d22 --- /dev/null +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -0,0 +1,33 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_purplei2p_i2pd_I2PD_JNI */ + +#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI +#define _Included_org_purplei2p_i2pd_I2PD_JNI +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_purplei2p_i2pd_I2PD_JNI + * Method: stringFromJNI + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith + (JNIEnv *, jclass); + +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels + (JNIEnv *, jclass); + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged + (JNIEnv * env, jclass clazz, jboolean isConnected); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/android/libs/.gitignore b/android/libs/.gitignore new file mode 100644 index 00000000..e4e4e6c1 --- /dev/null +++ b/android/libs/.gitignore @@ -0,0 +1 @@ +armeabi-v7a diff --git a/android/libs/android-support-v4.jar b/android/libs/android-support-v4.jar new file mode 100644 index 00000000..2ff47f4f Binary files /dev/null and b/android/libs/android-support-v4.jar differ diff --git a/android/proguard-project.txt b/android/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/android/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/project.properties b/android/project.properties new file mode 100644 index 00000000..7ce68660 --- /dev/null +++ b/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-24 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 00000000..a5dc7b68 Binary files /dev/null and b/android/res/drawable/icon.png differ diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 00000000..8fbe2468 Binary files /dev/null and b/android/res/drawable/itoopie_notification_icon.png differ diff --git a/android/res/menu/options_main.xml b/android/res/menu/options_main.xml new file mode 100644 index 00000000..388dfd83 --- /dev/null +++ b/android/res/menu/options_main.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100755 index 00000000..8c78e88b --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,9 @@ + + + i2pd + i2pd started + Quit + Graceful Quit + Graceful quit is already in progress + Graceful quit is in progress + diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java new file mode 100644 index 00000000..5e0ac4d0 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -0,0 +1,124 @@ +package org.purplei2p.i2pd; + +import java.util.HashSet; +import java.util.Set; + +import android.util.Log; + +public class DaemonSingleton { + private static final String TAG="i2pd"; + private static final DaemonSingleton instance = new DaemonSingleton(); + public static interface StateChangeListener { void daemonStateChanged(); } + private final Set stateChangeListeners = new HashSet(); + + public static DaemonSingleton getInstance() { + return instance; + } + + public synchronized void addStateChangeListener(StateChangeListener listener) { stateChangeListeners.add(listener); } + public synchronized void removeStateChangeListener(StateChangeListener listener) { stateChangeListeners.remove(listener); } + + public synchronized void stopAcceptingTunnels() { + if(isStartedOkay()){ + state=State.gracefulShutdownInProgress; + fireStateChange(); + I2PD_JNI.stopAcceptingTunnels(); + } + } + + public void onNetworkStateChange(boolean isConnected) { + I2PD_JNI.onNetworkStateChanged(isConnected); + } + + private boolean startedOkay; + + public static enum State {starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress}; + + private State state = State.starting; + + public State getState() { return state; } + + { + synchronized(this){ + fireStateChange(); + new Thread(new Runnable(){ + + @Override + public void run() { + try { + I2PD_JNI.loadLibraries(); + synchronized (DaemonSingleton.this) { + state = State.jniLibraryLoaded; + fireStateChange(); + } + } catch (Throwable tr) { + lastThrowable=tr; + synchronized (DaemonSingleton.this) { + state = State.startFailed; + fireStateChange(); + } + return; + } + try { + synchronized (DaemonSingleton.this) { + daemonStartResult = I2PD_JNI.startDaemon(); + if("ok".equals(daemonStartResult)){state=State.startedOkay;setStartedOkay(true);} + else state=State.startFailed; + fireStateChange(); + } + } catch (Throwable tr) { + lastThrowable=tr; + synchronized (DaemonSingleton.this) { + state = State.startFailed; + fireStateChange(); + } + return; + } + } + + }, "i2pdDaemonStart").start(); + } + } + private Throwable lastThrowable; + private String daemonStartResult="N/A"; + + private synchronized void fireStateChange() { + Log.i(TAG, "daemon state change: "+state); + for(StateChangeListener listener : stateChangeListeners) { + try { + listener.daemonStateChanged(); + } catch (Throwable tr) { + Log.e(TAG, "exception in listener ignored", tr); + } + } + } + + public Throwable getLastThrowable() { + return lastThrowable; + } + + public String getDaemonStartResult() { + return daemonStartResult; + } + + private final Object startedOkayLock = new Object(); + + public boolean isStartedOkay() { + synchronized (startedOkayLock) { + return startedOkay; + } + } + + private void setStartedOkay(boolean startedOkay) { + synchronized (startedOkayLock) { + this.startedOkay = startedOkay; + } + } + + public synchronized void stopDaemon() { + if(isStartedOkay()){ + try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);} + setStartedOkay(false); + } + } +} diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java new file mode 100644 index 00000000..6ff826c4 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -0,0 +1,88 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +public class ForegroundService extends Service { +// private NotificationManager mNM; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.i2pd_started; + + /** + * Class for clients to access. Because we know this service always + * runs in the same process as its clients, we don't need to deal with + * IPC. + */ + public class LocalBinder extends Binder { + ForegroundService getService() { + return ForegroundService.this; + } + } + + @Override + public void onCreate() { +// mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. We put an icon in the status bar. + showNotification(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("ForegroundService", "Received start id " + startId + ": " + intent); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + //mNM.cancel(NOTIFICATION); + stopForeground(true); + + // Tell the user we stopped. + //Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + // This is the object that receives interactions from clients. See + // RemoteService for a more complete example. + private final IBinder mBinder = new LocalBinder(); + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.i2pd_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PD.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.app_name)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java new file mode 100755 index 00000000..0397cf03 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -0,0 +1,243 @@ +package org.purplei2p.i2pd; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Timer; +import java.util.TimerTask; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +public class I2PD extends Activity { + private static final String TAG = "i2pd"; + private DaemonSingleton daemon = DaemonSingleton.getInstance(); + private DaemonSingleton.StateChangeListener daemonStateChangeListener = + new DaemonSingleton.StateChangeListener() { + + @Override + public void daemonStateChanged() { + runOnUiThread(new Runnable(){ + + @Override + public void run() { + try { + if(textView==null)return; + Throwable tr = daemon.getLastThrowable(); + if(tr!=null) { + textView.setText(throwableToString(tr)); + return; + } + DaemonSingleton.State state = daemon.getState(); + textView.setText(String.valueOf(state)+ + (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():"")); + } catch (Throwable tr) { + Log.e(TAG,"error ignored",tr); + } + } + }); + } + }; + + private TextView textView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //set the app be foreground (do not unload when RAM needed) + doBindService(); + + textView = new TextView(this); + setContentView(textView); + daemonStateChangeListener.daemonStateChanged(); + daemon.addStateChangeListener(daemonStateChangeListener); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + localDestroy(); + } + + private void localDestroy() { + textView = null; + daemon.removeStateChangeListener(daemonStateChangeListener); + Timer gracefulQuitTimer = getGracefulQuitTimer(); + if(gracefulQuitTimer!=null) { + gracefulQuitTimer.cancel(); + setGracefulQuitTimer(null); + } + try{ + doUnbindService(); + }catch(Throwable tr){ + Log.e(TAG, "", tr); + } + } + + private CharSequence throwableToString(Throwable tr) { + StringWriter sw = new StringWriter(8192); + PrintWriter pw = new PrintWriter(sw); + tr.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + +// private LocalService mBoundService; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. Because we have bound to a explicit + // service that we know is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. +// mBoundService = ((LocalService.LocalBinder)service).getService(); + + // Tell the user about this for our demo. +// Toast.makeText(Binding.this, R.string.local_service_connected, +// Toast.LENGTH_SHORT).show(); + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + // Because it is running in our same process, we should never + // see this happen. +// mBoundService = null; +// Toast.makeText(Binding.this, R.string.local_service_disconnected, +// Toast.LENGTH_SHORT).show(); + } + }; + + + private boolean mIsBound; + + private void doBindService() { + // Establish a connection with the service. We use an explicit + // class name because we want a specific service implementation that + // we know will be running in our own process (and thus won't be + // supporting component replacement by other applications). + bindService(new Intent(this, + ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + private void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.options_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + switch(id){ + case R.id.action_quit: + quit(); + return true; + case R.id.action_graceful_quit: + gracefulQuit(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressLint("NewApi") + private void quit() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + finishAndRemoveTask(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + finishAffinity(); + } else { + //moveTaskToBack(true); + finish(); + } + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + try{ + daemon.stopDaemon(); + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + System.exit(0); + } + + private Timer gracefulQuitTimer; + private final Object gracefulQuitTimerLock = new Object(); + private void gracefulQuit() { + if(getGracefulQuitTimer()!=null){ + Toast.makeText(this, R.string.graceful_quit_is_already_in_progress, + Toast.LENGTH_SHORT).show(); + return; + } + Toast.makeText(this, R.string.graceful_quit_is_in_progress, + Toast.LENGTH_SHORT).show(); + new Thread(new Runnable(){ + + @Override + public void run() { + try{ + Log.d(TAG, "grac stopping"); + if(daemon.isStartedOkay()) { + daemon.stopAcceptingTunnels(); + Timer gracefulQuitTimer = new Timer(true); + setGracefulQuitTimer(gracefulQuitTimer); + gracefulQuitTimer.schedule(new TimerTask(){ + + @Override + public void run() { + quit(); + } + + }, 10*60*1000/*milliseconds*/); + }else{ + quit(); + } + } catch(Throwable tr) { + Log.e(TAG,"",tr); + } + } + + },"gracQuitInit").start(); + } + + private Timer getGracefulQuitTimer() { + synchronized (gracefulQuitTimerLock) { + return gracefulQuitTimer; + } + } + + private void setGracefulQuitTimer(Timer gracefulQuitTimer) { + synchronized (gracefulQuitTimerLock) { + this.gracefulQuitTimer = gracefulQuitTimer; + } + } +} diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java new file mode 100644 index 00000000..5a3addbf --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -0,0 +1,21 @@ +package org.purplei2p.i2pd; + +public class I2PD_JNI { + public static native String getABICompiledWith(); + /** + * returns error info if failed + * returns "ok" if daemon initialized and started okay + */ + public static native String startDaemon(); + //should only be called after startDaemon() success + public static native void stopDaemon(); + + public static native void stopAcceptingTunnels(); + + public static native void onNetworkStateChanged(boolean isConnected); + + public static void loadLibraries() { + System.loadLibrary("gnustl_shared"); + System.loadLibrary("i2pd"); + } +} diff --git a/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java new file mode 100644 index 00000000..e2f284b0 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java @@ -0,0 +1,30 @@ +package org.purplei2p.i2pd; + +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkStateChangeReceiver extends BroadcastReceiver { + + private static final String TAG = "i2pd"; + + //api level 1 + @Override + public void onReceive(final Context context, final Intent intent) { + Log.d(TAG,"Network state change"); + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected(); + // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru + // boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI); + + I2PD_JNI.onNetworkStateChanged(isConnected); + } catch (Throwable tr) { + Log.d(TAG,"",tr); + } + } +} diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index edde2e06..7f9b8c66 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required ( VERSION 2.8.12 ) cmake_policy( VERSION 2.8.12 ) project ( "i2pd" ) +# for debugging +#set(CMAKE_VERBOSE_MAKEFILE on) + # configurale options option(WITH_AESNI "Use AES-NI instructions set" OFF) option(WITH_HARDENING "Use hardening compiler flags" OFF) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index cdde1ee7..795e408a 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -16,7 +16,7 @@ Let's clone the repository and start building the i2pd: git clone https://github.com/PurpleI2P/i2pd.git cd i2pd/build cmake -DCMAKE_BUILD_TYPE=Release # more options could be passed, see "CMake Options" -make +make # you may add VERBOSE=1 to cmdline for debugging ``` After successfull build i2pd could be installed with: diff --git a/docs/configuration.md b/docs/configuration.md index 1b3e899b..e6ac74d2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,7 +14,7 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --pidfile= - Where to write pidfile (dont write by default) * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) * --logfile= - Path to logfile (default - autodetect) -* --loglevel= - Log messages above this level (debug, *info, warn, error) +* --loglevel= - Log messages above this level (debug, info, warn, error) * --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...) * --host= - Router external IP for incoming connections * --port= - Port to listen for incoming connections (default: auto) @@ -68,6 +68,8 @@ All options below still possible in cmdline, but better write it in config file: * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified * --i2pcontrol.enabled= - If I2P control is enabled. false by default +* --upnp.enabled= - Enable or disable UPnP, false by default for CLI and true for GUI (Windows, Android) +* --upnp.name= - Name i2pd appears in UPnP forwardings list. I2Pd by default * --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 567f3d66..90ab6c10 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -14,7 +14,6 @@ MAIN_PATH = /path/to/libraries # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git - BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 0e2ca01c..1b8af253 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -11,8 +11,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, ui(new Ui::MainWindow)*/ #ifndef ANDROID - , - quitting(false) + ,quitting(false) #endif { //ui->setupUi(this); diff --git a/util.cpp b/util.cpp index 89bcda6c..08ee6672 100644 --- a/util.cpp +++ b/util.cpp @@ -7,7 +7,6 @@ #include #include #include -#include "Config.h" #include "util.h" #include "Log.h" @@ -460,6 +459,5 @@ namespace net #endif } } - } // util } // i2p diff --git a/util.h b/util.h index 7c393e02..642ecc9b 100644 --- a/util.h +++ b/util.h @@ -71,5 +71,4 @@ namespace util } } - #endif