Browse Source

Merge pull request #578 from PurpleI2P/openssl

recent changes
pull/580/head
orignal 8 years ago committed by GitHub
parent
commit
0e31da5e51
  1. 1
      .gitignore
  2. 324
      AddressBook.cpp
  3. 8
      AddressBook.h
  4. 39
      BOB.cpp
  5. 2
      BOB.h
  6. 12
      Config.cpp
  7. 74
      Daemon.cpp
  8. 14
      Daemon.h
  9. 2
      Destination.cpp
  10. 9
      HTTP.cpp
  11. 3
      HTTP.h
  12. 402
      HTTPProxy.cpp
  13. 97
      HTTPServer.cpp
  14. 9
      I2NPProtocol.cpp
  15. 2
      I2NPProtocol.h
  16. 2
      Makefile
  17. 12
      NTCPSession.cpp
  18. 1
      NTCPSession.h
  19. 40
      NetDb.cpp
  20. 14
      NetDb.h
  21. 68
      Reseed.cpp
  22. 31
      RouterContext.cpp
  23. 90
      RouterInfo.cpp
  24. 10
      RouterInfo.h
  25. 10
      Streaming.cpp
  26. 29
      Transports.cpp
  27. 6
      Transports.h
  28. 185
      UPnP.cpp
  29. 35
      UPnP.h
  30. 7
      android/.gitignore
  31. 25
      android/AndroidManifest.xml
  32. 97
      android/build.xml
  33. 113
      android/jni/Android.mk
  34. 32
      android/jni/Application.mk
  35. 194
      android/jni/DaemonAndroid.cpp
  36. 87
      android/jni/DaemonAndroid.h
  37. 66
      android/jni/i2pd_android.cpp
  38. 33
      android/jni/org_purplei2p_i2pd_I2PD_JNI.h
  39. 1
      android/libs/.gitignore
  40. BIN
      android/libs/android-support-v4.jar
  41. 20
      android/proguard-project.txt
  42. 14
      android/project.properties
  43. BIN
      android/res/drawable/icon.png
  44. BIN
      android/res/drawable/itoopie_notification_icon.png
  45. 16
      android/res/menu/options_main.xml
  46. 9
      android/res/values/strings.xml
  47. 124
      android/src/org/purplei2p/i2pd/DaemonSingleton.java
  48. 88
      android/src/org/purplei2p/i2pd/ForegroundService.java
  49. 243
      android/src/org/purplei2p/i2pd/I2PD.java
  50. 21
      android/src/org/purplei2p/i2pd/I2PD_JNI.java
  51. 30
      android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java
  52. 3
      build/CMakeLists.txt
  53. 2
      docs/build_notes_unix.md
  54. 4
      docs/configuration.md
  55. 1
      qt/i2pd_qt/i2pd_qt.pro
  56. 3
      qt/i2pd_qt/mainwindow.cpp
  57. 2
      util.cpp
  58. 1
      util.h

1
.gitignore vendored

@ -237,3 +237,4 @@ pip-log.txt
# Sphinx # Sphinx
docs/_build docs/_build
/androidIdea/

324
AddressBook.cpp

@ -11,6 +11,7 @@
#include "Identity.h" #include "Identity.h"
#include "FS.h" #include "FS.h"
#include "Log.h" #include "Log.h"
#include "HTTP.h"
#include "NetDb.h" #include "NetDb.h"
#include "ClientContext.h" #include "ClientContext.h"
#include "AddressBook.h" #include "AddressBook.h"
@ -339,12 +340,12 @@ namespace client
std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode
if (f.is_open ()) if (f.is_open ())
{ {
LoadHostsFromStream (f); LoadHostsFromStream (f, false);
m_IsLoaded = true; m_IsLoaded = true;
} }
} }
bool AddressBook::LoadHostsFromStream (std::istream& f) bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update)
{ {
std::unique_lock<std::mutex> l(m_AddressBookMutex); std::unique_lock<std::mutex> l(m_AddressBookMutex);
int numAddresses = 0; int numAddresses = 0;
@ -365,17 +366,18 @@ namespace client
std::string addr = s.substr(pos); std::string addr = s.substr(pos);
auto ident = std::make_shared<i2p::data::IdentityEx> (); auto ident = std::make_shared<i2p::data::IdentityEx> ();
if (ident->FromBase64(addr)) if (!ident->FromBase64(addr)) {
{
m_Addresses[name] = ident->GetIdentHash ();
m_Storage->AddAddress (ident);
numAddresses++;
}
else
{
LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name);
incomplete = f.eof (); 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 else
incomplete = f.eof (); incomplete = f.eof ();
@ -516,14 +518,15 @@ namespace client
if (!m_DefaultSubscription) if (!m_DefaultSubscription)
m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS)); m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS));
m_IsDownloading = true; m_IsDownloading = true;
m_DefaultSubscription->CheckSubscription (); m_DefaultSubscription->CheckUpdates ();
} }
else if (!m_Subscriptions.empty ()) else if (!m_Subscriptions.empty ())
{ {
// pick random subscription // pick random subscription
auto ind = rand () % m_Subscriptions.size(); auto ind = rand () % m_Subscriptions.size();
m_IsDownloading = true; m_IsDownloading = true;
m_Subscriptions[ind]->CheckSubscription (); std::thread load_hosts(&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]);
load_hosts.detach(); // TODO: use join
} }
} }
else else
@ -569,7 +572,7 @@ namespace client
ident = FindAddress (address.substr (dot + 1)); ident = FindAddress (address.substr (dot + 1));
if (!ident) if (!ident)
{ {
LogPrint (eLogError, "AddressBook: Can't find domain for ", address); LogPrint (eLogError, "Addressbook: Can't find domain for ", address);
return; return;
} }
@ -585,7 +588,7 @@ namespace client
std::unique_lock<std::mutex> l(m_LookupsMutex); std::unique_lock<std::mutex> l(m_LookupsMutex);
m_Lookups[nonce] = address; 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; size_t len = address.length () + 9;
uint8_t * buf = new uint8_t[len]; uint8_t * buf = new uint8_t[len];
memset (buf, 0, 4); memset (buf, 0, 4);
@ -602,11 +605,11 @@ namespace client
{ {
if (len < 44) if (len < 44)
{ {
LogPrint (eLogError, "AddressBook: Lookup response is too short ", len); LogPrint (eLogError, "Addressbook: Lookup response is too short ", len);
return; return;
} }
uint32_t nonce = bufbe32toh (buf + 4); 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::string address;
{ {
std::unique_lock<std::mutex> l(m_LookupsMutex); std::unique_lock<std::mutex> l(m_LookupsMutex);
@ -629,168 +632,153 @@ namespace client
{ {
} }
void AddressBookSubscription::CheckSubscription () void AddressBookSubscription::CheckUpdates ()
{ {
std::thread load_hosts(&AddressBookSubscription::Request, this); bool result = MakeRequest ();
load_hosts.detach(); // TODO: use join 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 // must be run in separate thread
LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link);
bool success = false; if (!url.parse(m_Link)) {
i2p::util::http::url u (m_Link); LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link);
i2p::data::IdentHash ident; return false;
if (m_Book.GetIdentHash (u.host_, ident)) }
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 ()) std::unique_lock<std::mutex> l(newDataReceivedMutex);
{ i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident,
// load ETag [&newDataReceived, &leaseSet](std::shared_ptr<i2p::data::LeaseSet> ls)
m_Book.GetEtag (ident, m_Etag, m_LastModified); {
LogPrint (eLogInfo, "Addressbook: set ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); leaseSet = ls;
} newDataReceived.notify_all ();
std::condition_variable newDataReceived; });
std::mutex newDataReceivedMutex; if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident);
if (!leaseSet)
{ {
std::unique_lock<std::mutex> l(newDataReceivedMutex); LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired");
i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident, i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident);
[&newDataReceived, &leaseSet](std::shared_ptr<i2p::data::LeaseSet> ls) return false;
{
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);
}
} }
if (leaseSet) }
{ if (!leaseSet) {
std::stringstream request, response; /* still no leaseset found */
// standard header LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found");
request << "GET " << u.path_ << " HTTP/1.1\r\n" return false;
<< "Host: " << u.host_ << "\r\n" }
<< "Accept: */*\r\n" if (m_Etag.empty() && m_LastModified.empty()) {
<< "User-Agent: Wget/1.11.4\r\n" m_Book.GetEtag (m_Ident, m_Etag, m_LastModified);
//<< "Accept-Encoding: gzip\r\n" LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified);
<< "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"; /* save url parts for later use */
if (m_Etag.length () > 0) // etag std::string dest_host = url.host;
request << i2p::util::http::IF_NONE_MATCH << ": " << m_Etag << "\r\n"; int dest_port = url.port ? url.port : 80;
if (m_LastModified.length () > 0) // if-modfief-since /* create http request & send it */
request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n"; i2p::http::HTTPReq req;
request << "\r\n"; // end of header req.add_header("Host", dest_host);
auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_); req.add_header("User-Agent", "Wget/1.11.4");
stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); req.add_header("Connection", "close");
if (!m_Etag.empty())
uint8_t buf[4096]; req.add_header("If-None-Match", m_Etag);
bool end = false; if (!m_LastModified.empty())
while (!end) req.add_header("If-Modified-Since", m_LastModified);
{ /* convert url to relative */
stream->AsyncReceive (boost::asio::buffer (buf, 4096), url.schema = "";
[&](const boost::system::error_code& ecode, std::size_t bytes_transferred) url.host = "";
{ req.uri = url.to_string();
if (bytes_transferred) auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port);
response.write ((char *)buf, bytes_transferred); std::string request = req.to_string();
if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) stream->Send ((const uint8_t *) request.data(), request.length());
end = true; /* read response */
newDataReceived.notify_all (); std::string response;
}, uint8_t recv_buf[4096];
30); // wait for 30 seconds bool end = false;
std::unique_lock<std::mutex> l(newDataReceivedMutex); while (!end) {
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096),
LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); [&](const boost::system::error_code& ecode, std::size_t bytes_transferred)
}
// 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
{ {
bool isChunked = false, isGzip = false; if (bytes_transferred)
m_Etag = ""; m_LastModified = ""; response.append ((char *)recv_buf, bytes_transferred);
std::string header, statusMessage; if (ecode == boost::asio::error::timed_out || !stream->IsOpen ())
std::getline (response, statusMessage); end = true;
// read until new line meaning end of header newDataReceived.notify_all ();
while (!response.eof () && header != "\r") },
{ 30); // wait for 30 seconds
std::getline (response, header); std::unique_lock<std::mutex> l(newDataReceivedMutex);
if (response.fail ()) break; if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
auto colon = header.find (':'); LogPrint (eLogError, "Addressbook: subscriptions request timeout expired");
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");
} }
else // process remaining buffer
LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_); while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) {
response.append ((char *)recv_buf, len);
if (!success) }
LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); /* parse response */
i2p::http::HTTPRes res;
m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified); int res_head_len = res.parse(response);
} if (res_head_len < 0) {
LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host);
bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip) return false;
{ }
if (isGzip) if (res_head_len == 0) {
{ LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout");
std::stringstream uncompressed; 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; i2p::data::GzipInflator inflator;
inflator.Inflate (s, uncompressed); inflator.Inflate ((const uint8_t *) response.data(), response.length(), out);
if (!uncompressed.fail ()) if (out.fail()) {
return m_Book.LoadHostsFromStream (uncompressed); LogPrint(eLogError, "Addressbook: can't gunzip http response");
else
return false; return false;
} }
else response = out.str();
return m_Book.LoadHostsFromStream (s); }
std::stringstream ss(response);
LogPrint (eLogInfo, "Addressbook: got update from ", dest_host);
m_Book.LoadHostsFromStream (ss, true);
return true;
} }
AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination): AddressResolver::AddressResolver (std::shared_ptr<ClientDestination> destination):
@ -821,7 +809,7 @@ namespace client
{ {
if (len < 9 || len < buf[8] + 9U) 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; return;
} }
// read requested address // read requested address
@ -829,7 +817,7 @@ namespace client
char address[255]; char address[255];
memcpy (address, buf + 9, l); memcpy (address, buf + 9, l);
address[l] = 0; address[l] = 0;
LogPrint (eLogDebug, "AddressBook: Address request ", address); LogPrint (eLogDebug, "Addressbook: Address request ", address);
// send response // send response
uint8_t response[44]; uint8_t response[44];
memset (response, 0, 4); // reserved memset (response, 0, 4); // reserved

8
AddressBook.h

@ -70,7 +70,7 @@ namespace client
void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (const std::string& address, const std::string& base64); // for jump service
void InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address); void InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> 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); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified);
//This method returns the ".b32.i2p" address //This method returns the ".b32.i2p" address
std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); }
@ -112,17 +112,17 @@ namespace client
public: public:
AddressBookSubscription (AddressBook& book, const std::string& link); AddressBookSubscription (AddressBook& book, const std::string& link);
void CheckSubscription (); void CheckUpdates ();
private: private:
void Request (); bool MakeRequest ();
bool ProcessResponse (std::stringstream& s, bool isGzip = false);
private: private:
AddressBook& m_Book; AddressBook& m_Book;
std::string m_Link, m_Etag, m_LastModified; std::string m_Link, m_Etag, m_LastModified;
i2p::data::IdentHash m_Ident;
// m_Etag must be surrounded by "" // m_Etag must be surrounded by ""
}; };

39
BOB.cpp

@ -202,9 +202,9 @@ namespace client
} }
BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0), m_Owner (owner), m_Socket (m_Owner.GetService ()),
m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0), m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false),
m_CurrentDestination (nullptr) m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr)
{ {
} }
@ -364,9 +364,9 @@ namespace client
if (m_OutPort && !m_Address.empty ()) if (m_OutPort && !m_Address.empty ())
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
m_CurrentDestination->Start (); m_CurrentDestination->Start ();
SendReplyOK ("tunnel starting"); SendReplyOK ("tunnel starting");
} }
void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
{ {
auto dest = m_Owner.FindDestination (m_Nickname); auto dest = m_Owner.FindDestination (m_Nickname);
@ -523,6 +523,34 @@ namespace client
else else
SendReplyError ("malformed"); 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): BOBCommandChannel::BOBCommandChannel (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr), m_IsRunning (false), m_Thread (nullptr),
@ -548,6 +576,7 @@ namespace client
m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler;
} }
BOBCommandChannel::~BOBCommandChannel () BOBCommandChannel::~BOBCommandChannel ()

2
BOB.h

@ -36,6 +36,7 @@ namespace client
const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_CLEAR[] = "clear";
const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_LIST[] = "list";
const char BOB_COMMAND_OPTION[] = "option"; 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_VERSION[] = "BOB 00.00.10\nOK\n";
const char BOB_REPLY_OK[] = "OK %s\n"; const char BOB_REPLY_OK[] = "OK %s\n";
@ -168,6 +169,7 @@ namespace client
void ClearCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len);
void ListCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len);
void OptionCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len);
void StatusCommandHandler (const char * operand, size_t len);
private: private:

12
Config.cpp

@ -31,6 +31,7 @@ namespace config {
#ifdef MESHNET #ifdef MESHNET
nat = false; nat = false;
#endif #endif
options_description general("General options"); options_description general("General options");
general.add_options() general.add_options()
("help", "Show this message") ("help", "Show this message")
@ -126,6 +127,16 @@ namespace config {
("i2pcontrol.key", value<std::string>()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ("i2pcontrol.key", value<std::string>()->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<bool>()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding")
("upnp.name", value<std::string>()->default_value("I2Pd"), "Name i2pd appears in UPnP forwardings list")
;
options_description precomputation("Precomputation options"); options_description precomputation("Precomputation options");
precomputation.add_options() precomputation.add_options()
("precomputation.elgamal", ("precomputation.elgamal",
@ -153,6 +164,7 @@ namespace config {
.add(bob) .add(bob)
.add(i2cp) .add(i2cp)
.add(i2pcontrol) .add(i2pcontrol)
.add(upnp)
.add(precomputation) .add(precomputation)
.add(trust) .add(trust)
; ;

74
Daemon.cpp

@ -22,11 +22,8 @@
#include "I2PControl.h" #include "I2PControl.h"
#include "ClientContext.h" #include "ClientContext.h"
#include "Crypto.h" #include "Crypto.h"
#include "util.h"
#ifdef USE_UPNP
#include "UPnP.h" #include "UPnP.h"
#endif #include "util.h"
namespace i2p namespace i2p
{ {
@ -40,10 +37,7 @@ namespace i2p
std::unique_ptr<i2p::http::HTTPServer> httpServer; std::unique_ptr<i2p::http::HTTPServer> httpServer;
std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService; std::unique_ptr<i2p::client::I2PControlService> m_I2PControlService;
std::unique_ptr<i2p::transport::UPnP> UPnP;
#ifdef USE_UPNP
i2p::transport::UPnP m_UPnP;
#endif
}; };
Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {}
@ -130,47 +124,16 @@ namespace i2p
ipv4 = false; ipv4 = false;
ipv6 = true; ipv6 = true;
#endif #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); uint16_t port; i2p::config::GetOption("port", port);
if (!i2p::config::IsDefault("port")) if (!i2p::config::IsDefault("port"))
{ {
LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port);
i2p::context.UpdatePort (port); i2p::context.UpdatePort (port);
} }
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV6 (ipv6);
i2p::context.SetSupportsV4 (ipv4); i2p::context.SetSupportsV4 (ipv4);
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetAcceptsTunnels (!transit); i2p::context.SetAcceptsTunnels (!transit);
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
SetMaxNumTransitTunnels (transitTunnels); SetMaxNumTransitTunnels (transitTunnels);
@ -249,21 +212,24 @@ namespace i2p
LogPrint(eLogInfo, "Daemon: starting NetDB"); LogPrint(eLogInfo, "Daemon: starting NetDB");
i2p::data::netdb.Start(); i2p::data::netdb.Start();
#ifdef USE_UPNP bool upnp; i2p::config::GetOption("upnp.enabled", upnp);
LogPrint(eLogInfo, "Daemon: starting UPnP"); if (upnp) {
d.m_UPnP.Start (); d.UPnP = std::unique_ptr<i2p::transport::UPnP>(new i2p::transport::UPnP);
#endif d.UPnP->Start ();
}
bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ntcp; i2p::config::GetOption("ntcp", ntcp);
bool ssu; i2p::config::GetOption("ssu", ssu); bool ssu; i2p::config::GetOption("ssu", ssu);
LogPrint(eLogInfo, "Daemon: starting Transports"); LogPrint(eLogInfo, "Daemon: starting Transports");
if(!ssu) LogPrint(eLogDebug, "Daemon: ssu disabled"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled");
if(!ntcp) LogPrint(eLogDebug, "Daemon: ntcp disabled"); if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled");
i2p::transport::transports.Start(ntcp, ssu); i2p::transport::transports.Start(ntcp, ssu);
if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) {
LogPrint(eLogInfo, "Daemon: Transports started"); LogPrint(eLogInfo, "Daemon: Transports started");
} else { } else {
LogPrint(eLogError, "Daemon: failed to start Transports"); LogPrint(eLogError, "Daemon: failed to start Transports");
/** shut down netdb right away */ /** shut down netdb right away */
i2p::transport::transports.Stop();
i2p::data::netdb.Stop(); i2p::data::netdb.Stop();
return false; return false;
} }
@ -304,10 +270,12 @@ namespace i2p
i2p::client::context.Stop(); i2p::client::context.Stop();
LogPrint(eLogInfo, "Daemon: stopping Tunnels"); LogPrint(eLogInfo, "Daemon: stopping Tunnels");
i2p::tunnel::tunnels.Stop(); i2p::tunnel::tunnels.Stop();
#ifdef USE_UPNP
LogPrint(eLogInfo, "Daemon: stopping UPnP"); if (d.UPnP) {
d.m_UPnP.Stop (); d.UPnP->Stop ();
#endif d.UPnP = nullptr;
}
LogPrint(eLogInfo, "Daemon: stopping Transports"); LogPrint(eLogInfo, "Daemon: stopping Transports");
i2p::transport::transports.Stop(); i2p::transport::transports.Stop();
LogPrint(eLogInfo, "Daemon: stopping NetDB"); LogPrint(eLogInfo, "Daemon: stopping NetDB");

14
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) #elif defined(_WIN32)
#define Daemon i2p::util::DaemonWin32::Instance() #define Daemon i2p::util::DaemonWin32::Instance()
class DaemonWin32 : public Daemon_Singleton class DaemonWin32 : public Daemon_Singleton

2
Destination.cpp

@ -470,7 +470,7 @@ namespace client
else else
LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32());
// we have to publish again // we have to publish again
s->Publish (); s->Publish ();
}); });
} }
} }

9
HTTP.cpp

@ -278,6 +278,15 @@ namespace http {
return false; 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() { long int HTTPMsg::content_length() {
unsigned long int length = 0; unsigned long int length = 0;
auto it = headers.find("Content-Length"); auto it = headers.find("Content-Length");

3
HTTP.h

@ -118,6 +118,9 @@ namespace http {
/** @brief Checks that response declared as chunked data */ /** @brief Checks that response declared as chunked data */
bool is_chunked(); bool is_chunked();
/** @brief Checks that response contains compressed data */
bool is_gzipped();
}; };
/** /**

402
HTTPProxy.cpp

@ -1,6 +1,5 @@
#include <cstring> #include <cstring>
#include <cassert> #include <cassert>
#include <boost/lexical_cast.hpp>
#include <string> #include <string>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
@ -23,6 +22,21 @@
namespace i2p { namespace i2p {
namespace proxy { namespace proxy {
std::map<std::string, std::string> jumpservices = {
{ "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" },
{ "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" },
};
static const char *pageHead =
"<head>\r\n"
" <title>I2P HTTP proxy: error</title>\r\n"
" <style type=\"text/css\">\r\n"
" body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n"
" .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n"
" </style>\r\n"
"</head>\r\n"
;
bool str_rmatch(std::string & str, const char *suffix) { bool str_rmatch(std::string & str, const char *suffix) {
auto pos = str.rfind (suffix); auto pos = str.rfind (suffix);
if (pos == std::string::npos) if (pos == std::string::npos)
@ -32,51 +46,34 @@ namespace proxy {
return false; return false;
} }
static const size_t http_buffer_size = 8192;
class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPReqHandler> class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPReqHandler>
{ {
private: private:
enum state
{ bool HandleRequest();
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);
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void Terminate(); void Terminate();
void AsyncSockRead(); void AsyncSockRead();
void HTTPRequestFailed(const char *message); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64);
void RedirectToJumpService(std::string & host); void SanitizeHTTPRequest(i2p::http::HTTPReq & req);
void ExtractRequest();
bool ValidateHTTPRequest();
void HandleJumpServices();
bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len);
void SentHTTPFailed(const boost::system::error_code & ecode); void SentHTTPFailed(const boost::system::error_code & ecode);
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream); void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> 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<boost::asio::ip::tcp::socket> m_sock; std::shared_ptr<boost::asio::ip::tcp::socket> 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: public:
HTTPReqHandler(HTTPProxy * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) : HTTPReqHandler(HTTPProxy * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
I2PServiceHandler(parent), m_sock(sock) I2PServiceHandler(parent), m_sock(sock) {}
{ EnterState(GET_METHOD); }
~HTTPReqHandler() { Terminate(); } ~HTTPReqHandler() { Terminate(); }
void Handle () { AsyncSockRead(); } void Handle () { AsyncSockRead(); } /* overload */
}; };
void HTTPReqHandler::AsyncSockRead() void HTTPReqHandler::AsyncSockRead()
@ -86,7 +83,7 @@ namespace proxy {
LogPrint(eLogError, "HTTPProxy: no socket for read"); LogPrint(eLogError, "HTTPProxy: no socket for read");
return; 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::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2)); std::placeholders::_1, std::placeholders::_2));
} }
@ -102,210 +99,192 @@ namespace proxy {
Done(shared_from_this()); Done(shared_from_this());
} }
/* All hope is lost beyond this point */ void HTTPReqHandler::GenericProxyError(const char *title, const char *description) {
//TODO: handle this apropriately std::stringstream ss;
void HTTPReqHandler::HTTPRequestFailed(const char *message) ss << "<h1>Proxy error: " << title << "</h1>\r\n";
ss << "<p>" << description << "</p>\r\n";
std::string content = ss.str();
SendProxyError(content);
}
void HTTPReqHandler::HostNotFound(std::string & host) {
std::stringstream ss;
ss << "<h1>Proxy error: Host not found</h1>\r\n"
<< "<p>Remote host not found in router's addressbook</p>\r\n"
<< "<p>You may try to find this host on jumpservices below:</p>\r\n"
<< "<ul>\r\n";
for (auto & js : jumpservices) {
ss << " <li><a href=\"" << js.second << host << "\">" << js.first << "</a></li>\r\n";
}
ss << "</ul>\r\n";
std::string content = ss.str();
SendProxyError(content);
}
void HTTPReqHandler::SendProxyError(std::string & content)
{ {
i2p::http::HTTPRes res; i2p::http::HTTPRes res;
res.code = 500; 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.add_header("Connection", "close");
res.body = message; std::stringstream ss;
res.body += "\r\n"; ss << "<html>\r\n" << pageHead
<< "<body>" << content << "</body>\r\n"
<< "</html>\r\n";
res.body = ss.str();
std::string response = res.to_string(); std::string response = res.to_string();
boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::async_write(*m_sock, boost::asio::buffer(response),
std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); 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; const char *param = "i2paddresshelper=";
i2p::http::URL url; std::size_t pos = url.query.find(param);
std::size_t len = std::strlen(param);
/* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ std::map<std::string, std::string> params;
i2p::config::GetOption("http.address", url.host);
i2p::config::GetOption("http.port", url.port);
url.path = "/";
url.query = "page=jumpservices&address=";
url.query += host;
res.code = 302; /* redirect */ if (pos == std::string::npos)
res.add_header("Location", url.to_string().c_str()); return false; /* not found */
if (!url.parse_query(params))
return false;
std::string response = res.to_string(); std::string value = params["i2paddresshelper"];
boost::asio::async_write(*m_sock, boost::asio::buffer(response), len += value.length();
std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); 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<std::string> 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; i2p::http::URL url;
url.parse (m_url); std::string b64;
m_address = url.host; int req_len = 0;
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);
}
bool HTTPReqHandler::ValidateHTTPRequest() req_len = req.parse(m_recv_buf);
{
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;
}
void HTTPReqHandler::HandleJumpServices() if (req_len == 0)
{ return false; /* need more data */
static const char * helpermark1 = "?i2paddresshelper=";
static const char * helpermark2 = "&i2paddresshelper="; if (req_len < 0) {
size_t addressHelperPos1 = m_path.rfind (helpermark1); LogPrint(eLogError, "HTTPProxy: unable to parse request");
size_t addressHelperPos2 = m_path.rfind (helpermark2); GenericProxyError("Invalid request", "Proxy unable to parse your request");
size_t addressHelperPos; return true; /* parse error */
if (addressHelperPos1 == std::string::npos)
{
if (addressHelperPos2 == std::string::npos)
return; //Not a jump service
else
addressHelperPos = addressHelperPos2;
} }
else
{ /* parsing success, now let's look inside request */
if (addressHelperPos2 == std::string::npos) LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri);
addressHelperPos = addressHelperPos1; url.parse(req.uri);
else if ( addressHelperPos1 > addressHelperPos2 )
addressHelperPos = addressHelperPos1; if (ExtractAddressHelper(url, b64)) {
else i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64);
addressHelperPos = addressHelperPos2; 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 <a href=\"" << full_url << "\">here</a> 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) SanitizeHTTPRequest(req);
{
ExtractRequest(); //TODO: parse earlier std::string dest_host = url.host;
if (!ValidateHTTPRequest()) return false; uint16_t dest_port = url.port;
HandleJumpServices(); /* 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; i2p::data::IdentHash identHash;
if (str_rmatch(m_address, ".i2p")) if (str_rmatch(dest_host, ".i2p")) {
{ if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) {
if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ HostNotFound(dest_host);
RedirectToJumpService(m_address); return true; /* request processed */
return false;
} }
/* 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; /* make relative url */
m_request.push_back(' '); url.schema = "";
m_request += m_path; url.host = "";
m_request.push_back(' '); req.uri = url.to_string();
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<const char *>(http_buff),len);
return true;
}
bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) /* drop original request from recv buffer */
{ m_recv_buf.erase(0, req_len);
while (len > 0) /* build new buffer from modified request and data from original request */
{ m_send_buf = req.to_string();
//TODO: fallback to finding HOst: header if needed m_send_buf.append(m_recv_buf);
switch (m_state) /* connect to destination */
{ LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port);
case GET_METHOD: GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete,
switch (*http_buff) shared_from_this(), std::placeholders::_1), dest_host, dest_port);
{
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);
}
return true; return true;
} }
/* will be called after some data received from client */
void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) 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) if(ecode)
{ {
LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode);
@ -313,17 +292,12 @@ namespace proxy {
return; return;
} }
if (HandleData(m_http_buff, len)) m_recv_buf.append(reinterpret_cast<const char *>(m_recv_chunk), len);
{ if (HandleRequest()) {
if (m_state == DONE) m_recv_buf.clear();
{ return;
LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url);
GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete,
shared_from_this(), std::placeholders::_1), m_address, m_port);
}
else
AsyncSockRead();
} }
AsyncSockRead();
} }
void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode)
@ -337,15 +311,15 @@ namespace proxy {
{ {
if (!stream) { if (!stream) {
LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); 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; return;
} }
if (Kill()) if (Kill())
return; 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<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream); auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream);
GetOwner()->AddHandler (connection); GetOwner()->AddHandler (connection);
connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_request.data()), m_request.size()); connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_send_buf.data()), m_send_buf.length());
Done (shared_from_this()); Done (shared_from_this());
} }

97
HTTPServer.cpp

@ -71,24 +71,18 @@ namespace http {
const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions";
const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session";
const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels";
const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices";
const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_PAGE_COMMANDS[] = "commands";
const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; const char HTTP_PAGE_LEASESETS[] = "leasesets";
const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; 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_START[] = "shutdown_start";
const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel";
const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate";
const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test";
const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; 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_SAM_SESSION_ID[] = "id";
const char HTTP_PARAM_ADDRESS[] = "address"; const char HTTP_PARAM_ADDRESS[] = "address";
std::map<std::string, std::string> 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) { void ShowUptime (std::stringstream& s, int seconds) {
int num; int num;
@ -147,11 +141,11 @@ namespace http {
" <a href=\"/\">Main page</a><br>\r\n<br>\r\n" " <a href=\"/\">Main page</a><br>\r\n<br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_COMMANDS << "\">Router commands</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_COMMANDS << "\">Router commands</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">Local destinations</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">Local destinations</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_LEASESETS << "\">Lease Sets</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TUNNELS << "\">Tunnels</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_TUNNELS << "\">Tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">Transit tunnels</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">Transit tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_TRANSPORTS << "\">Transports</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_TRANSPORTS << "\">Transports</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_I2P_TUNNELS << "\">I2P tunnels</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_I2P_TUNNELS << "\">I2P tunnels</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_JUMPSERVICES << "\">Jump services</a><br>\r\n"
" <a href=\"/?page=" << HTTP_PAGE_SAM_SESSIONS << "\">SAM sessions</a><br>\r\n" " <a href=\"/?page=" << HTTP_PAGE_SAM_SESSIONS << "\">SAM sessions</a><br>\r\n"
"</div>\r\n" "</div>\r\n"
"<div class=right>"; "<div class=right>";
@ -247,20 +241,6 @@ namespace http {
s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n"; s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n";
} }
void ShowJumpServices (std::stringstream& s, const std::string& address)
{
s << "<form method=\"get\" action=\"/\">";
s << "<input type=\"hidden\" name=\"page\" value=\"jumpservices\">";
s << "<input type=\"text\" name=\"address\" value=\"" << address << "\">";
s << "<input type=\"submit\" value=\"Update\">";
s << "</form><br>\r\n";
s << "<b>Jump services for " << address << "</b>\r\n<ul>\r\n";
for (auto & js : jumpservices) {
s << " <li><a href=\"" << js.second << address << "\">" << js.first << "</a></li>\r\n";
}
s << "</ul>\r\n";
}
void ShowLocalDestinations (std::stringstream& s) void ShowLocalDestinations (std::stringstream& s)
{ {
s << "<b>Local Destinations:</b><br>\r\n<br>\r\n"; s << "<b>Local Destinations:</b><br>\r\n<br>\r\n";
@ -349,6 +329,57 @@ namespace http {
} }
} }
void ShowLeasesSets(std::stringstream& s)
{
s << "<div id='leasesets'>LeaseSets</div><br>";
// for each lease set
i2p::data::netdb.VisitLeaseSets(
[&s](const i2p::data::IdentHash dest, std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
// create copy of lease set so we extract leases
i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen());
// begin lease set entry
s << "<div class='leaseset";
if (ls.IsExpired())
s << " expired"; // additional css class for expired
s << "'>";
// invalid ?
if (!ls.IsValid())
s << "<div class='invalid'>!! Invalid !! </div>";
// ident
s << "<div class='ident'>" << dest.ToBase32() << "</div>";
// LeaseSet time
s << "<div class='expires'>expires: " << ls.GetExpirationTime() << "</div>";
// get non expired leases
auto leases = ls.GetNonExpiredLeases();
// show non expired leases
s << "<div class='leasecount'>Non Expired Leases: " << leases.size() << "</div>";
// for each lease
s << "<div class='leases'>";
for ( auto & l : leases )
{
// begin lease
s << "<div class='lease'>";
// gateway
s << "<div class='gateway'>Gateway: " << l->tunnelGateway.ToBase64() << "</div>";
// tunnel id
s << "<div class='tunnelID'>TunnelID: " << l->tunnelID << "</div>";
// end date
s << "<div class='endDate'>EndDate: " << l->endDate << "</div>";
// end lease
s << "</div>";
}
// end for each lease
s << "</div>";
// end lease set entry
s << "</div>";
// linebreak
s << "<br>";
}
);
// end for each lease set
}
void ShowTunnels (std::stringstream& s) void ShowTunnels (std::stringstream& s)
{ {
s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n"; s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n";
@ -374,10 +405,10 @@ namespace http {
s << " <a href=\"/?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "\">Run peer test</a><br>\r\n"; s << " <a href=\"/?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "\">Run peer test</a><br>\r\n";
//s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n"; //s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n";
if (i2p::context.AcceptsTunnels ()) if (i2p::context.AcceptsTunnels ())
s << " <a href=\"/?cmd=" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << "\">Stop accepting tunnels</a><br>\r\n"; s << " <a href=\"/?cmd=" << HTTP_COMMAND_DISABLE_TRANSIT << "\">Decline transit tunnels</a><br>\r\n";
else else
s << " <a href=\"/?cmd=" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << "\">Start accepting tunnels</a><br>\r\n"; s << " <a href=\"/?cmd=" << HTTP_COMMAND_ENABLE_TRANSIT << "\">Accept transit tunnels</a><br>\r\n";
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID))
if (Daemon.gracefullShutdownInterval) { if (Daemon.gracefullShutdownInterval) {
s << " <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "\">Cancel gracefull shutdown ("; s << " <a href=\"/?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "\">Cancel gracefull shutdown (";
s << Daemon.gracefullShutdownInterval; s << Daemon.gracefullShutdownInterval;
@ -649,8 +680,6 @@ namespace http {
ShowTunnels (s); ShowTunnels (s);
else if (page == HTTP_PAGE_COMMANDS) else if (page == HTTP_PAGE_COMMANDS)
ShowCommands (s); ShowCommands (s);
else if (page == HTTP_PAGE_JUMPSERVICES)
ShowJumpServices (s, params["address"]);
else if (page == HTTP_PAGE_TRANSIT_TUNNELS) else if (page == HTTP_PAGE_TRANSIT_TUNNELS)
ShowTransitTunnels (s); ShowTransitTunnels (s);
else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) else if (page == HTTP_PAGE_LOCAL_DESTINATIONS)
@ -663,6 +692,8 @@ namespace http {
ShowSAMSession (s, params["sam_id"]); ShowSAMSession (s, params["sam_id"]);
else if (page == HTTP_PAGE_I2P_TUNNELS) else if (page == HTTP_PAGE_I2P_TUNNELS)
ShowI2PTunnels (s); ShowI2PTunnels (s);
else if (page == HTTP_PAGE_LEASESETS)
ShowLeasesSets(s);
else { else {
res.code = 400; res.code = 400;
ShowError(s, "Unknown page: " + page); ShowError(s, "Unknown page: " + page);
@ -684,18 +715,18 @@ namespace http {
i2p::transport::transports.PeerTest (); i2p::transport::transports.PeerTest ();
else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) else if (cmd == HTTP_COMMAND_RELOAD_CONFIG)
i2p::client::context.ReloadConfig (); i2p::client::context.ReloadConfig ();
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT)
i2p::context.SetAcceptsTunnels (true); i2p::context.SetAcceptsTunnels (true);
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT)
i2p::context.SetAcceptsTunnels (false); i2p::context.SetAcceptsTunnels (false);
else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { else if (cmd == HTTP_COMMAND_SHUTDOWN_START) {
i2p::context.SetAcceptsTunnels (false); i2p::context.SetAcceptsTunnels (false);
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID))
Daemon.gracefullShutdownInterval = 10*60; Daemon.gracefullShutdownInterval = 10*60;
#endif #endif
} else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) {
i2p::context.SetAcceptsTunnels (true); i2p::context.SetAcceptsTunnels (true);
#if (!defined(WIN32) && !defined(QT_GUI_LIB)) #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID))
Daemon.gracefullShutdownInterval = 0; Daemon.gracefullShutdownInterval = 0;
#endif #endif
} else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) {

9
I2NPProtocol.cpp

@ -165,9 +165,10 @@ namespace i2p
buf += 32; buf += 32;
memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW
buf += 32; buf += 32;
*buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags
htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf ++;
buf += 5; htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID
buf += 4;
// excluded // excluded
htobe16buf (buf, cnt); htobe16buf (buf, cnt);
@ -182,7 +183,7 @@ namespace i2p
} }
// encryption // encryption
memcpy (buf, replyKey, 32); memcpy (buf, replyKey, 32);
buf[32] = 1; // 1 tag buf[32] = uint8_t( 1 ); // 1 tag
memcpy (buf + 33, replyTag, 32); memcpy (buf + 33, replyTag, 32);
buf += 65; buf += 65;

2
I2NPProtocol.h

@ -90,7 +90,7 @@ namespace i2p
// DatabaseLookup flags // DatabaseLookup flags
const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; 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_FLAGS_MASK = 0x0C;
const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0;
const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100

2
Makefile

@ -33,7 +33,7 @@ else # win32 mingw
endif endif
ifeq ($(USE_MESHNET),yes) ifeq ($(USE_MESHNET),yes)
CXXFLAGS += -DMESHNET NEEDED_CXXFLAGS += -DMESHNET
endif endif
all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD)

12
NTCPSession.cpp

@ -713,8 +713,16 @@ namespace transport
if (m_IsTerminated) return; if (m_IsTerminated) return;
if (m_IsSending) if (m_IsSending)
{ {
for (auto it: msgs) if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE)
m_SendQueue.push_back (it); {
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 else
Send (msgs); Send (msgs);

1
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 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_BAN_EXPIRATION_TIMEOUT = 70; // in second
const int NTCP_CLOCK_SKEW = 60; // in seconds 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 NTCPServer;
class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession> class NTCPSession: public TransportSession, public std::enable_shared_from_this<NTCPSession>

40
NetDb.cpp

@ -213,6 +213,7 @@ namespace data
bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len,
std::shared_ptr<i2p::tunnel::InboundTunnel> from) std::shared_ptr<i2p::tunnel::InboundTunnel> from)
{ {
std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
bool updated = false; bool updated = false;
if (!from) // unsolicited LS must be received directly if (!from) // unsolicited LS must be received directly
{ {
@ -264,6 +265,7 @@ namespace data
std::shared_ptr<LeaseSet> NetDb::FindLeaseSet (const IdentHash& destination) const std::shared_ptr<LeaseSet> NetDb::FindLeaseSet (const IdentHash& destination) const
{ {
std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
auto it = m_LeaseSets.find (destination); auto it = m_LeaseSets.find (destination);
if (it != m_LeaseSets.end ()) if (it != m_LeaseSets.end ())
return it->second; return it->second;
@ -318,6 +320,13 @@ namespace data
return true; return true;
} }
void NetDb::VisitLeaseSets(LeaseSetVisitor v)
{
std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
for ( auto & entry : m_LeaseSets)
v(entry.first, entry.second);
}
void NetDb::Load () void NetDb::Load ()
{ {
// make sure we cleanup netDb from previous attempts // make sure we cleanup netDb from previous attempts
@ -623,17 +632,18 @@ namespace data
char key[48]; char key[48];
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key[l] = 0; key[l] = 0;
uint8_t flag = buf[64];
IdentHash replyIdent(buf + 32); IdentHash replyIdent(buf + 32);
uint8_t flag = buf[64];
LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag);
uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK;
const uint8_t * excluded = buf + 65; const uint8_t * excluded = buf + 65;
uint32_t replyTunnelID = 0; uint32_t replyTunnelID = 0;
if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel
{ {
replyTunnelID = bufbe32toh (buf + 65); replyTunnelID = bufbe32toh (excluded);
excluded += 4; excluded += 4;
} }
uint16_t numExcluded = bufbe16toh (excluded); uint16_t numExcluded = bufbe16toh (excluded);
@ -641,7 +651,7 @@ namespace data
if (numExcluded > 512) if (numExcluded > 512)
{ {
LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512"); LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512");
numExcluded = 0; // TODO: return;
} }
std::shared_ptr<I2NPMessage> replyMsg; std::shared_ptr<I2NPMessage> replyMsg;
@ -714,35 +724,39 @@ namespace data
} }
if (!found) if (!found)
{ {
std::set<IdentHash> excludedRouters; std::set<IdentHash> excludedRouters;
const uint8_t * exclude_ident = excluded;
for (int i = 0; i < numExcluded; i++) for (int i = 0; i < numExcluded; i++)
{ {
excludedRouters.insert (excluded); excludedRouters.insert (exclude_ident);
excluded += 32; exclude_ident += 32;
} }
closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true);
if (!numExcluded) // save if no excluded if (!numExcluded) // save if no excluded
m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ()); m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ());
} }
replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills);
} }
} }
excluded += numExcluded * 32;
if (replyMsg) if (replyMsg)
{ {
if (replyTunnelID) if (replyTunnelID)
{ {
// encryption might be used though tunnel only // 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; const uint8_t * sessionKey = excluded;
uint8_t numTags = sessionKey[32]; const uint8_t numTags = excluded[32];
if (numTags > 0) 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); i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag);
replyMsg = garlic.WrapSingleMessage (replyMsg); 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 exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;

14
NetDb.h

@ -31,12 +31,10 @@ namespace data
const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60;
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 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; const int NETDB_PUBLISH_INTERVAL = 60*40;
#endif
/** function for visiting a leaseset stored in a floodfill */
typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor;
class NetDb class NetDb
{ {
@ -85,7 +83,10 @@ namespace data
int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumRouters () const { return m_RouterInfos.size (); };
int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); };
int GetNumLeaseSets () const { return m_LeaseSets.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); };
/** visit all lease sets we currently store */
void VisitLeaseSets(LeaseSetVisitor v);
private: private:
void Load (); void Load ();
@ -103,6 +104,7 @@ namespace data
private: private:
mutable std::mutex m_LeaseSetsMutex;
std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets; std::map<IdentHash, std::shared_ptr<LeaseSet> > m_LeaseSets;
mutable std::mutex m_RouterInfosMutex; mutable std::mutex m_RouterInfosMutex;
std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos; std::map<IdentHash, std::shared_ptr<RouterInfo> > m_RouterInfos;

68
Reseed.cpp

@ -14,7 +14,7 @@
#include "Log.h" #include "Log.h"
#include "Identity.h" #include "Identity.h"
#include "NetDb.h" #include "NetDb.h"
#include "util.h" #include "HTTP.h"
namespace i2p namespace i2p
{ {
@ -372,13 +372,19 @@ namespace data
std::string Reseeder::HttpsRequest (const std::string& address) std::string Reseeder::HttpsRequest (const std::string& address)
{ {
i2p::util::http::url u(address); i2p::http::URL url;
if (u.port_ == 80) u.port_ = 443; 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::asio::io_service service;
boost::system::error_code ecode; boost::system::error_code ecode;
auto it = boost::asio::ip::tcp::resolver(service).resolve ( auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode); boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
if (!ecode) if (!ecode)
{ {
boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); 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); s.handshake (boost::asio::ssl::stream_base::client, ecode);
if (!ecode) if (!ecode)
{ {
LogPrint (eLogInfo, "Reseed: Connected to ", u.host_, ":", u.port_); LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port);
// send request i2p::http::HTTPReq req;
std::stringstream ss; req.uri = url.to_string();
ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_ req.add_header("User-Agent", "Wget/1.11.4");
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n"; req.add_header("Connection", "close");
s.write_some (boost::asio::buffer (ss.str ())); s.write_some (boost::asio::buffer (req.to_string()));
// read response // read response
std::stringstream rs; std::stringstream rs;
char response[1024]; size_t l = 0; char recv_buf[1024]; size_t l = 0;
do do {
{ l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode);
l = s.read_some (boost::asio::buffer (response, 1024), ecode); if (l) rs.write (recv_buf, l);
if (l) rs.write (response, l); } while (!ecode && l);
}
while (!ecode && l);
// process response // 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 else
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ());
} }
else else
LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ()); LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ());
} }
else else
LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ()); LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ());
return ""; return "";
} }
} }

31
RouterContext.cpp

@ -49,10 +49,19 @@ namespace i2p
uint16_t port; i2p::config::GetOption("port", port); uint16_t port; i2p::config::GetOption("port", port);
if (!port) if (!port)
port = rand () % (30777 - 9111) + 9111; // I2P network ports range port = rand () % (30777 - 9111) + 9111; // I2P network ports range
std::string host; i2p::config::GetOption("host", host); bool ipv4; i2p::config::GetOption("ipv4", ipv4);
if (i2p::config::IsDefault("host")) bool ipv6; i2p::config::GetOption("ipv6", ipv6);
host = "127.0.0.1"; // replace default address with safe value bool nat; i2p::config::GetOption("nat", nat);
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); 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.AddNTCPAddress (host.c_str(), port);
routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | routerInfo.SetCaps (i2p::data::RouterInfo::eReachable |
i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC 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 m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B
// remove NTCP address // remove NTCP address
auto& addresses = m_RouterInfo.GetAddresses (); 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; break;
} }
} }
@ -253,12 +263,13 @@ namespace i2p
// insert NTCP back // insert NTCP back
auto& addresses = m_RouterInfo.GetAddresses (); 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 // 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; break;
} }
} }

90
RouterInfo.cpp

@ -15,10 +15,16 @@ namespace i2p
{ {
namespace data namespace data
{ {
RouterInfo::RouterInfo (): m_Buffer (nullptr)
{
m_Addresses = std::make_shared<Addresses>(); // create empty list
}
RouterInfo::RouterInfo (const std::string& fullPath): RouterInfo::RouterInfo (const std::string& fullPath):
m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false),
m_SupportedTransports (0), m_Caps (0) m_SupportedTransports (0), m_Caps (0)
{ {
m_Addresses = std::make_shared<Addresses>(); // create empty list
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
ReadFromFile (); ReadFromFile ();
} }
@ -26,6 +32,7 @@ namespace data
RouterInfo::RouterInfo (const uint8_t * buf, int len): RouterInfo::RouterInfo (const uint8_t * buf, int len):
m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0)
{ {
m_Addresses = std::make_shared<Addresses>(); // create empty list
m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE];
memcpy (m_Buffer, buf, len); memcpy (m_Buffer, buf, len);
m_BufferLen = len; m_BufferLen = len;
@ -48,7 +55,7 @@ namespace data
m_IsUnreachable = false; m_IsUnreachable = false;
m_SupportedTransports = 0; m_SupportedTransports = 0;
m_Caps = 0; m_Caps = 0;
m_Addresses.clear (); // don't clean up m_Addresses, it will be replaced in ReadFromStream
m_Properties.clear (); m_Properties.clear ();
// copy buffer // copy buffer
if (!m_Buffer) if (!m_Buffer)
@ -144,6 +151,7 @@ namespace data
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
m_Timestamp = be64toh (m_Timestamp); m_Timestamp = be64toh (m_Timestamp);
// read addresses // read addresses
auto addresses = std::make_shared<Addresses>();
uint8_t numAddresses; uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return;
bool introducers = false; bool introducers = false;
@ -234,10 +242,11 @@ namespace data
} }
if (isValidAddress) if (isValidAddress)
{ {
m_Addresses.push_back(std::make_shared<Address>(address)); addresses->push_back(std::make_shared<Address>(address));
m_SupportedTransports |= supportedTransports; m_SupportedTransports |= supportedTransports;
} }
} }
m_Addresses = addresses;
// read peers // read peers
uint8_t numPeers; uint8_t numPeers;
s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
@ -288,7 +297,7 @@ namespace data
if (!s) return; if (!s) return;
} }
if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers)) if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers))
SetUnreachable (true); SetUnreachable (true);
} }
@ -366,9 +375,9 @@ namespace data
s.write ((char *)&ts, sizeof (ts)); s.write ((char *)&ts, sizeof (ts));
// addresses // addresses
uint8_t numAddresses = m_Addresses.size (); uint8_t numAddresses = m_Addresses->size ();
s.write ((char *)&numAddresses, sizeof (numAddresses)); s.write ((char *)&numAddresses, sizeof (numAddresses));
for (auto addr : m_Addresses) for (auto addr : *m_Addresses)
{ {
Address& address = *addr; Address& address = *addr;
s.write ((char *)&address.cost, sizeof (address.cost)); s.write ((char *)&address.cost, sizeof (address.cost));
@ -561,9 +570,9 @@ namespace data
addr->cost = 2; addr->cost = 2;
addr->date = 0; addr->date = 0;
addr->mtu = 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; if (*it == *addr) return;
m_Addresses.push_back(addr); m_Addresses->push_back(addr);
m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4;
} }
@ -577,9 +586,9 @@ namespace data
addr->date = 0; addr->date = 0;
addr->mtu = mtu; addr->mtu = mtu;
memcpy (addr->key, key, 32); 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; if (*it == *addr) return;
m_Addresses.push_back(addr); m_Addresses->push_back(addr);
m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
m_Caps |= eSSUTesting; m_Caps |= eSSUTesting;
m_Caps |= eSSUIntroducer; m_Caps |= eSSUIntroducer;
@ -587,7 +596,7 @@ namespace data
bool RouterInfo::AddIntroducer (const Introducer& introducer) bool RouterInfo::AddIntroducer (const Introducer& introducer)
{ {
for (auto addr : m_Addresses) for (auto addr : *m_Addresses)
{ {
if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{ {
@ -602,7 +611,7 @@ namespace data
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) 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 ()) if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ())
{ {
@ -691,28 +700,14 @@ namespace data
{ {
if (IsV6 ()) if (IsV6 ())
{ {
// NTCP m_SupportedTransports &= ~(eNTCPV6 | eSSUV6);
m_SupportedTransports &= ~eNTCPV6; for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
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++)
{ {
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && auto addr = *it;
m_Addresses[i]->host.is_v6 ()) if (addr->host.is_v6 ())
{ it = m_Addresses->erase (it);
m_Addresses.erase (m_Addresses.begin () + i); else
break; it++;
}
} }
} }
} }
@ -721,28 +716,14 @@ namespace data
{ {
if (IsV4 ()) if (IsV4 ())
{ {
// NTCP m_SupportedTransports &= ~(eNTCPV4 | eSSUV4);
m_SupportedTransports &= ~eNTCPV4; for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
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++)
{ {
if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && auto addr = *it;
m_Addresses[i]->host.is_v4 ()) if (addr->host.is_v4 ())
{ it = m_Addresses->erase (it);
m_Addresses.erase (m_Addresses.begin () + i); else
break; it++;
}
} }
} }
} }
@ -770,7 +751,8 @@ namespace data
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const std::shared_ptr<const RouterInfo::Address> 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) if (address->transportStyle == s)
{ {

10
RouterInfo.h

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector> #include <vector>
#include <list>
#include <iostream> #include <iostream>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include "Identity.h" #include "Identity.h"
@ -105,9 +106,10 @@ namespace data
return !(*this == other); return !(*this == other);
} }
}; };
typedef std::list<std::shared_ptr<Address> > Addresses;
RouterInfo ();
RouterInfo (const std::string& fullPath); RouterInfo (const std::string& fullPath);
RouterInfo (): m_Buffer (nullptr) { };
RouterInfo (const RouterInfo& ) = default; RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default;
RouterInfo (const uint8_t * buf, int len); RouterInfo (const uint8_t * buf, int len);
@ -117,7 +119,7 @@ namespace data
void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity); void SetRouterIdentity (std::shared_ptr<const IdentityEx> identity);
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; }; uint64_t GetTimestamp () const { return m_Timestamp; };
std::vector<std::shared_ptr<Address> >& GetAddresses () { return m_Addresses; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr
std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const; std::shared_ptr<const Address> GetNTCPAddress (bool v4only = true) const;
std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const; std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
std::shared_ptr<const Address> GetSSUV6Address () const; std::shared_ptr<const Address> GetSSUV6Address () const;
@ -199,7 +201,7 @@ namespace data
uint8_t * m_Buffer; uint8_t * m_Buffer;
size_t m_BufferLen; size_t m_BufferLen;
uint64_t m_Timestamp; uint64_t m_Timestamp;
std::vector<std::shared_ptr<Address> > m_Addresses; std::shared_ptr<Addresses> m_Addresses;
std::map<std::string, std::string> m_Properties; std::map<std::string, std::string> m_Properties;
bool m_IsUpdated, m_IsUnreachable; bool m_IsUpdated, m_IsUnreachable;
uint8_t m_SupportedTransports, m_Caps; uint8_t m_SupportedTransports, m_Caps;

10
Streaming.cpp

@ -201,7 +201,7 @@ namespace stream
memset (const_cast<uint8_t *>(optionData), 0, signatureLen); memset (const_cast<uint8_t *>(optionData), 0, signatureLen);
if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) 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 (); Close ();
flags |= PACKET_FLAG_CLOSE; flags |= PACKET_FLAG_CLOSE;
} }
@ -222,6 +222,7 @@ namespace stream
if (flags & PACKET_FLAG_RESET) 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; m_Status = eStreamStatusReset;
Close (); Close ();
} }
@ -495,6 +496,7 @@ namespace stream
void Stream::Close () void Stream::Close ()
{ {
LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status);
switch (m_Status) switch (m_Status)
{ {
case eStreamStatusOpen: case eStreamStatusOpen:
@ -668,7 +670,7 @@ namespace stream
// check for resend attempts // check for resend attempts
if (m_NumResendAttempts >= MAX_NUM_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; m_Status = eStreamStatusReset;
Close (); Close ();
return; return;
@ -703,7 +705,7 @@ namespace stream
case 4: case 4:
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
UpdateCurrentRemoteLease (); // pick another lease 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; break;
case 3: case 3:
// pick another outbound tunnel // pick another outbound tunnel
@ -725,7 +727,7 @@ namespace stream
{ {
if (m_LastReceivedSequenceNumber < 0) 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; m_Status = eStreamStatusReset;
Close (); Close ();
return; return;

29
Transports.cpp

@ -93,7 +93,7 @@ namespace transport
Transports transports; Transports transports;
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_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_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
@ -114,7 +114,7 @@ namespace transport
auto& addresses = context.GetRouterInfo ().GetAddresses (); auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (auto address : addresses) for (auto address : addresses)
{ {
if (!m_NTCPServer && enableNTCP) if (m_NTCPServer == nullptr && enableNTCP)
{ {
m_NTCPServer = new NTCPServer (); m_NTCPServer = new NTCPServer ();
m_NTCPServer->Start (); m_NTCPServer->Start ();
@ -129,7 +129,7 @@ namespace transport
if (address->transportStyle == RouterInfo::eTransportSSU) if (address->transportStyle == RouterInfo::eTransportSSU)
{ {
if (!m_SSUServer && enableSSU) if (m_SSUServer == nullptr && enableSSU)
{ {
if (address->host.is_v4()) if (address->host.is_v4())
m_SSUServer = new SSUServer (address->port); m_SSUServer = new SSUServer (address->port);
@ -264,8 +264,17 @@ namespace transport
it->second.sessions.front ()->SendI2NPMessages (msgs); it->second.sessions.front ()->SendI2NPMessages (msgs);
else else
{ {
for (auto it1: msgs) if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES)
it->second.delayedMessages.push_back (it1); {
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<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
}
} }
} }
@ -337,7 +346,7 @@ namespace transport
} }
LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); LogPrint (eLogError, "Transports: No NTCP or SSU addresses available");
peer.Done (); peer.Done ();
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident); m_Peers.erase (ident);
return false; return false;
} }
@ -369,7 +378,7 @@ namespace transport
else else
{ {
LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages");
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it); m_Peers.erase (it);
} }
} }
@ -413,7 +422,7 @@ namespace transport
} }
} }
LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ());
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it1); m_Peers.erase (it1);
} }
} }
@ -455,7 +464,7 @@ namespace transport
} }
} }
LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ());
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it1); m_Peers.erase (it1);
} }
} }
@ -590,7 +599,7 @@ namespace transport
ConnectToPeer (ident, it->second); ConnectToPeer (ident, it->second);
else else
{ {
std::unique_lock<std::mutex> l(m_PeersMutex); std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it); m_Peers.erase (it);
} }
} }

6
Transports.h

@ -66,6 +66,7 @@ namespace transport
}; };
const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds
const int MAX_NUM_DELAYED_MESSAGES = 50;
class Transports class Transports
{ {
public: public:
@ -79,6 +80,9 @@ namespace transport
bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } bool IsBoundNTCP() const { return m_NTCPServer != nullptr; }
bool IsBoundSSU() const { return m_SSUServer != 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; }; boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair (); std::shared_ptr<i2p::crypto::DHKeys> GetNextDHKeysPair ();
void ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair); void ReuseDHKeysPair (std::shared_ptr<i2p::crypto::DHKeys> pair);
@ -132,7 +136,7 @@ namespace transport
private: private:
bool m_IsRunning; bool m_IsOnline, m_IsRunning;
std::thread * m_Thread; std::thread * m_Thread;
boost::asio::io_service m_Service; boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work; boost::asio::io_service::work m_Work;

185
UPnP.cpp

@ -13,6 +13,7 @@
#include "NetDb.h" #include "NetDb.h"
#include "util.h" #include "util.h"
#include "RouterInfo.h" #include "RouterInfo.h"
#include "Config.h"
#include <miniupnpc/miniupnpc.h> #include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h> #include <miniupnpc/upnpcommands.h>
@ -21,47 +22,54 @@ namespace i2p
{ {
namespace transport namespace transport
{ {
UPnP::UPnP () : m_Thread (nullptr) UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service)
{ {
} }
void UPnP::Stop () void UPnP::Stop ()
{ {
if (m_Thread) if (m_IsRunning)
{ {
m_Thread->join (); LogPrint(eLogInfo, "UPnP: stopping");
delete m_Thread; m_IsRunning = false;
m_Thread = nullptr; m_Timer.cancel ();
} m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
m_Thread.reset (nullptr);
}
CloseMapping ();
Close ();
}
} }
void UPnP::Start() 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 () UPnP::~UPnP ()
{ {
Stop ();
} }
void UPnP::Run () void UPnP::Run ()
{ {
const std::vector<std::shared_ptr<i2p::data::RouterInfo::Address> > a = context.GetRouterInfo().GetAddresses(); while (m_IsRunning)
for (auto address : a) {
{ try
if (!address->host.is_v6 ()) {
{ m_Service.run ();
Discover (); }
if (address->transportStyle == data::RouterInfo::eTransportSSU ) catch (std::exception& ex)
{ {
TryPortMapping (I2P_UPNP_UDP, address->port); LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ());
} }
else if (address->transportStyle == data::RouterInfo::eTransportNTCP ) }
{
TryPortMapping (I2P_UPNP_TCP, address->port);
}
}
}
} }
void UPnP::Discover () void UPnP::Discover ()
@ -80,81 +88,82 @@ namespace transport
r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress);
if(r != UPNPCOMMAND_SUCCESS) if(r != UPNPCOMMAND_SUCCESS)
{ {
LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress () returned ", r); LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress() returned ", r);
return; return;
} }
else else
{ {
if (m_externalIPAddress[0]) if (!m_externalIPAddress[0])
{ {
LogPrint (eLogInfo, "UPnP: ExternalIPAddress = ", m_externalIPAddress); LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed.");
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress));
return;
}
else
{
LogPrint (eLogError, "UPnP: GetExternalIPAddress failed.");
return; 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) void UPnP::PortMapping ()
{ {
std::string strType, strPort (std::to_string (port)); auto a = context.GetRouterInfo().GetAddresses();
switch (type) for (auto address : a)
{ {
case I2P_UPNP_TCP: if (!address->host.is_v6 ())
strType = "TCP"; TryPortMapping (address);
break; }
case I2P_UPNP_UDP: m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes
default: m_Timer.async_wait ([this](const boost::system::error_code& ecode)
strType = "UDP"; {
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<i2p::data::RouterInfo::Address> address)
{
std::string strType (GetProto (address)), strPort (std::to_string (address->port));
int r; int r;
std::string strDesc = "I2Pd"; std::string strDesc; i2p::config::GetOption("upnp.name", strDesc);
try { 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");
for (;;) { if (r!=UPNPCOMMAND_SUCCESS)
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;
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
}
} }
catch (boost::thread_interrupted) else
{ {
CloseMapping(type, port); LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")");
Close(); return;
throw;
} }
} }
void UPnP::CloseMapping (int type, int port) void UPnP::CloseMapping (std::shared_ptr<i2p::data::RouterInfo::Address> address)
{ {
std::string strType, strPort (std::to_string (port)); std::string strType (GetProto (address)), strPort (std::to_string (address->port));
switch (type)
{
case I2P_UPNP_TCP:
strType = "TCP";
break;
case I2P_UPNP_UDP:
default:
strType = "UDP";
}
int r = 0; int r = 0;
r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 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 () void UPnP::Close ()
@ -164,9 +173,23 @@ namespace transport
FreeUPNPUrls (&m_upnpUrls); FreeUPNPUrls (&m_upnpUrls);
} }
std::string UPnP::GetProto (std::shared_ptr<i2p::data::RouterInfo::Address> address)
{
switch (address->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP:
return "TCP";
break;
case i2p::data::RouterInfo::eTransportSSU:
default:
return "UDP";
}
}
} }
} }
#else /* USE_UPNP */
namespace i2p {
#endif namespace transport {
}
}
#endif /* USE_UPNP */

35
UPnP.h

@ -4,6 +4,7 @@
#ifdef USE_UPNP #ifdef USE_UPNP
#include <string> #include <string>
#include <thread> #include <thread>
#include <memory>
#include <miniupnpc/miniwget.h> #include <miniupnpc/miniwget.h>
#include <miniupnpc/miniupnpc.h> #include <miniupnpc/miniupnpc.h>
@ -14,9 +15,6 @@
#include "util.h" #include "util.h"
#define I2P_UPNP_TCP 1
#define I2P_UPNP_UDP 2
namespace i2p namespace i2p
{ {
namespace transport namespace transport
@ -32,13 +30,23 @@ namespace transport
void Start (); void Start ();
void Stop (); void Stop ();
void Discover ();
void TryPortMapping (int type, int port);
void CloseMapping (int type, int port);
private: private:
void Discover ();
void PortMapping ();
void TryPortMapping (std::shared_ptr<i2p::data::RouterInfo::Address> address);
void CloseMapping ();
void CloseMapping (std::shared_ptr<i2p::data::RouterInfo::Address> address);
void Run (); void Run ();
std::string GetProto (std::shared_ptr<i2p::data::RouterInfo::Address> address);
std::thread * m_Thread; private:
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_service m_Service;
boost::asio::deadline_timer m_Timer;
struct UPNPUrls m_upnpUrls; struct UPNPUrls m_upnpUrls;
struct IGDdatas m_upnpData; 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 // USE_UPNP
#endif // __UPNP_H__ #endif // __UPNP_H__

7
android/.gitignore vendored

@ -0,0 +1,7 @@
gen
tests
.idea
local.properties
build.sh
bin
log*

25
android/AndroidManifest.xml

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.purplei2p.i2pd"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application android:label="@string/app_name" android:allowBackup="true" android:icon="@drawable/icon">
<receiver android:name=".NetworkStateChangeReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<activity android:name=".I2PD"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:enabled="true" android:name=".ForegroundService"/>
</application>
</manifest>

97
android/build.xml

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="i2pd" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Insert sdk.dir=... into './local.properties'. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<fail
message="ndk.dir is missing. Insert ndk.dir=... into './local.properties'."
unless="ndk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

113
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)

32
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

194
android/jni/DaemonAndroid.cpp

@ -0,0 +1,194 @@
#include "DaemonAndroid.h"
#include "../../Daemon.h"
#include <iostream>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>
#include <exception>
//#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();
}
}
}

87
android/jni/DaemonAndroid.h

@ -0,0 +1,87 @@
#ifndef DAEMON_ANDROID_H
#define DAEMON_ANDROID_H
#include <string>
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

66
android/jni/i2pd_android.cpp

@ -0,0 +1,66 @@
//#include <string.h>
#include <jni.h>
#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);
}

33
android/jni/org_purplei2p_i2pd_I2PD_JNI.h

@ -0,0 +1,33 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

1
android/libs/.gitignore vendored

@ -0,0 +1 @@
armeabi-v7a

BIN
android/libs/android-support-v4.jar

Binary file not shown.

20
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 *;
#}

14
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

BIN
android/res/drawable/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
android/res/drawable/itoopie_notification_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

16
android/res/menu/options_main.xml

@ -0,0 +1,16 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".I2PD">
<item
android:id="@+id/action_graceful_quit"
android:title="@string/action_graceful_quit"
android:orderInCategory="98"
/>
<item
android:id="@+id/action_quit"
android:title="@string/action_quit"
android:orderInCategory="99"
/>
</menu>

9
android/res/values/strings.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">i2pd</string>
<string name="i2pd_started">i2pd started</string>
<string name="action_quit">Quit</string>
<string name="action_graceful_quit">Graceful Quit</string>
<string name="graceful_quit_is_already_in_progress">Graceful quit is already in progress</string>
<string name="graceful_quit_is_in_progress">Graceful quit is in progress</string>
</resources>

124
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<StateChangeListener> stateChangeListeners = new HashSet<StateChangeListener>();
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);
}
}
}

88
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);
}
}

243
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;
}
}
}

21
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");
}
}

30
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);
}
}
}

3
build/CMakeLists.txt

@ -3,6 +3,9 @@ cmake_minimum_required ( VERSION 2.8.12 )
cmake_policy( VERSION 2.8.12 ) cmake_policy( VERSION 2.8.12 )
project ( "i2pd" ) project ( "i2pd" )
# for debugging
#set(CMAKE_VERBOSE_MAKEFILE on)
# configurale options # configurale options
option(WITH_AESNI "Use AES-NI instructions set" OFF) option(WITH_AESNI "Use AES-NI instructions set" OFF)
option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_HARDENING "Use hardening compiler flags" OFF)

2
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 git clone https://github.com/PurpleI2P/i2pd.git
cd i2pd/build cd i2pd/build
cmake -DCMAKE_BUILD_TYPE=Release # more options could be passed, see "CMake Options" 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: After successfull build i2pd could be installed with:

4
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) * --pidfile= - Where to write pidfile (dont write by default)
* --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)
* --logfile= - Path to logfile (default - autodetect) * --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, ...) * --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...)
* --host= - Router external IP for incoming connections * --host= - Router external IP for incoming connections
* --port= - Port to listen for incoming connections (default: auto) * --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.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified
* --i2pcontrol.enabled= - If I2P control is enabled. false by default * --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 * --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 * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default

1
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/OpenSSL-for-Android-Prebuilt.git
# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git
# git clone https://github.com/PurpleI2P/android-ifaddrs.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git
BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt
OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt
MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt

3
qt/i2pd_qt/mainwindow.cpp

@ -11,8 +11,7 @@ MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)/*, QMainWindow(parent)/*,
ui(new Ui::MainWindow)*/ ui(new Ui::MainWindow)*/
#ifndef ANDROID #ifndef ANDROID
, ,quitting(false)
quitting(false)
#endif #endif
{ {
//ui->setupUi(this); //ui->setupUi(this);

2
util.cpp

@ -7,7 +7,6 @@
#include <set> #include <set>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include "Config.h"
#include "util.h" #include "util.h"
#include "Log.h" #include "Log.h"
@ -460,6 +459,5 @@ namespace net
#endif #endif
} }
} }
} // util } // util
} // i2p } // i2p

1
util.h

@ -71,5 +71,4 @@ namespace util
} }
} }
#endif #endif

Loading…
Cancel
Save