diff --git a/AddressBook.cpp b/AddressBook.cpp index 3cf2bc56..cd15d68d 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -11,6 +11,7 @@ #include "Identity.h" #include "FS.h" #include "Log.h" +#include "HTTP.h" #include "NetDb.h" #include "ClientContext.h" #include "AddressBook.h" @@ -339,12 +340,12 @@ namespace client std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { - LoadHostsFromStream (f); + LoadHostsFromStream (f, false); m_IsLoaded = true; } } - bool AddressBook::LoadHostsFromStream (std::istream& f) + bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; @@ -365,17 +366,18 @@ namespace client std::string addr = s.substr(pos); auto ident = std::make_shared (); - if (ident->FromBase64(addr)) - { - m_Addresses[name] = ident->GetIdentHash (); - m_Storage->AddAddress (ident); - numAddresses++; - } - else - { + if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); incomplete = f.eof (); + continue; } + numAddresses++; + if (m_Addresses.count(name) > 0) + continue; /* already exists */ + m_Addresses[name] = ident->GetIdentHash (); + m_Storage->AddAddress (ident); + if (is_update) + LogPrint(eLogInfo, "Addressbook: added new host: ", name); } else incomplete = f.eof (); @@ -516,14 +518,15 @@ namespace client if (!m_DefaultSubscription) m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS)); m_IsDownloading = true; - m_DefaultSubscription->CheckSubscription (); + m_DefaultSubscription->CheckUpdates (); } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; - m_Subscriptions[ind]->CheckSubscription (); + std::thread load_hosts(&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]); + load_hosts.detach(); // TODO: use join } } else @@ -569,7 +572,7 @@ namespace client ident = FindAddress (address.substr (dot + 1)); if (!ident) { - LogPrint (eLogError, "AddressBook: Can't find domain for ", address); + LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; } @@ -585,7 +588,7 @@ namespace client std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } - LogPrint (eLogDebug, "AddressBook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); + LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); size_t len = address.length () + 9; uint8_t * buf = new uint8_t[len]; memset (buf, 0, 4); @@ -602,11 +605,11 @@ namespace client { if (len < 44) { - LogPrint (eLogError, "AddressBook: Lookup response is too short ", len); + LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); return; } uint32_t nonce = bufbe32toh (buf + 4); - LogPrint (eLogDebug, "AddressBook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); + LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); std::string address; { std::unique_lock l(m_LookupsMutex); @@ -629,168 +632,153 @@ namespace client { } - void AddressBookSubscription::CheckSubscription () + void AddressBookSubscription::CheckUpdates () { - std::thread load_hosts(&AddressBookSubscription::Request, this); - load_hosts.detach(); // TODO: use join + bool result = MakeRequest (); + m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } - void AddressBookSubscription::Request () + bool AddressBookSubscription::MakeRequest () { + i2p::http::URL url; // must be run in separate thread - LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - bool success = false; - i2p::util::http::url u (m_Link); - i2p::data::IdentHash ident; - if (m_Book.GetIdentHash (u.host_, ident)) + LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); + if (!url.parse(m_Link)) { + LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); + return false; + } + if (!m_Book.GetIdentHash (url.host, m_Ident)) { + LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); + return false; + } + /* this code block still needs some love */ + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); + if (!leaseSet) { - if (!m_Etag.length ()) - { - // load ETag - m_Book.GetEtag (ident, m_Etag, m_LastModified); - LogPrint (eLogInfo, "Addressbook: set ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - } - std::condition_variable newDataReceived; - std::mutex newDataReceivedMutex; - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); - if (!leaseSet) + std::unique_lock l(newDataReceivedMutex); + i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, + [&newDataReceived, &leaseSet](std::shared_ptr ls) + { + leaseSet = ls; + newDataReceived.notify_all (); + }); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { - std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident, - [&newDataReceived, &leaseSet](std::shared_ptr ls) - { - leaseSet = ls; - newDataReceived.notify_all (); - }); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - { - LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); - i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident); - } + LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident); + return false; } - if (leaseSet) - { - std::stringstream request, response; - // standard header - request << "GET " << u.path_ << " HTTP/1.1\r\n" - << "Host: " << u.host_ << "\r\n" - << "Accept: */*\r\n" - << "User-Agent: Wget/1.11.4\r\n" - //<< "Accept-Encoding: gzip\r\n" - << "X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n" - << "Connection: close\r\n"; - if (m_Etag.length () > 0) // etag - request << i2p::util::http::IF_NONE_MATCH << ": " << m_Etag << "\r\n"; - if (m_LastModified.length () > 0) // if-modfief-since - request << i2p::util::http::IF_MODIFIED_SINCE << ": " << m_LastModified << "\r\n"; - request << "\r\n"; // end of header - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_); - stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); - - uint8_t buf[4096]; - bool end = false; - while (!end) - { - stream->AsyncReceive (boost::asio::buffer (buf, 4096), - [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (bytes_transferred) - response.write ((char *)buf, bytes_transferred); - if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) - end = true; - newDataReceived.notify_all (); - }, - 30); // wait for 30 seconds - std::unique_lock l(newDataReceivedMutex); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); - } - // process remaining buffer - while (size_t len = stream->ReadSome (buf, 4096)) - response.write ((char *)buf, len); - - // parse response - std::string version; - response >> version; // HTTP version - int status = 0; - response >> status; // status - if (status == 200) // OK + } + if (!leaseSet) { + /* still no leaseset found */ + LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); + return false; + } + if (m_Etag.empty() && m_LastModified.empty()) { + m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); + LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); + } + /* save url parts for later use */ + std::string dest_host = url.host; + int dest_port = url.port ? url.port : 80; + /* create http request & send it */ + i2p::http::HTTPReq req; + req.add_header("Host", dest_host); + req.add_header("User-Agent", "Wget/1.11.4"); + req.add_header("Connection", "close"); + if (!m_Etag.empty()) + req.add_header("If-None-Match", m_Etag); + if (!m_LastModified.empty()) + req.add_header("If-Modified-Since", m_LastModified); + /* convert url to relative */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); + std::string request = req.to_string(); + stream->Send ((const uint8_t *) request.data(), request.length()); + /* read response */ + std::string response; + uint8_t recv_buf[4096]; + bool end = false; + while (!end) { + stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), + [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) { - bool isChunked = false, isGzip = false; - m_Etag = ""; m_LastModified = ""; - std::string header, statusMessage; - std::getline (response, statusMessage); - // read until new line meaning end of header - while (!response.eof () && header != "\r") - { - std::getline (response, header); - if (response.fail ()) break; - auto colon = header.find (':'); - if (colon != std::string::npos) - { - std::string field = header.substr (0, colon); - boost::to_lower (field); // field are not case-sensitive - colon++; - header.resize (header.length () - 1); // delete \r - if (field == i2p::util::http::ETAG) - m_Etag = header.substr (colon + 1); - else if (field == i2p::util::http::LAST_MODIFIED) - m_LastModified = header.substr (colon + 1); - else if (field == i2p::util::http::TRANSFER_ENCODING) - isChunked = !header.compare (colon + 1, std::string::npos, "chunked"); - else if (field == i2p::util::http::CONTENT_ENCODING) - isGzip = !header.compare (colon + 1, std::string::npos, "gzip") || - !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); - } - } - LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - if (!response.eof () && !response.fail ()) - { - if (!isChunked) - success = ProcessResponse (response, isGzip); - else - { - // merge chunks - std::stringstream merged; - i2p::util::http::MergeChunkedResponse (response, merged); - success = ProcessResponse (merged, isGzip); - } - } - } - else if (status == 304) - { - success = true; - LogPrint (eLogInfo, "Addressbook: no updates from ", m_Link); - } - else - LogPrint (eLogWarning, "Adressbook: HTTP response ", status); - } - else - LogPrint (eLogError, "Addressbook: address ", u.host_, " not found"); + if (bytes_transferred) + response.append ((char *)recv_buf, bytes_transferred); + if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) + end = true; + newDataReceived.notify_all (); + }, + 30); // wait for 30 seconds + std::unique_lock l(newDataReceivedMutex); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) + LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); } - else - LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_); - - if (!success) - LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); - - m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified); - } - - bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip) - { - if (isGzip) - { - std::stringstream uncompressed; + // process remaining buffer + while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) { + response.append ((char *)recv_buf, len); + } + /* parse response */ + i2p::http::HTTPRes res; + int res_head_len = res.parse(response); + if (res_head_len < 0) { + LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); + return false; + } + if (res_head_len == 0) { + LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); + return false; + } + /* assert: res_head_len > 0 */ + response.erase(0, res_head_len); + if (res.code == 304) { + LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); + return false; + } + if (res.code != 200) { + LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); + return false; + } + int len = res.content_length(); + if (response.empty()) { + LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); + return false; + } + if (len > 0 && len != (int) response.length()) { + LogPrint(eLogError, "Addressbook: response size mismatch, expected: ", response.length(), ", got: ", len, "bytes"); + return false; + } + /* assert: res.code == 200 */ + auto it = res.headers.find("ETag"); + if (it != res.headers.end()) { + m_Etag = it->second; + } + it = res.headers.find("If-Modified-Since"); + if (it != res.headers.end()) { + m_LastModified = it->second; + } + if (res.is_chunked()) { + std::stringstream in(response), out; + i2p::http::MergeChunkedResponse (in, out); + response = out.str(); + } else if (res.is_gzipped()) { + std::stringstream out; i2p::data::GzipInflator inflator; - inflator.Inflate (s, uncompressed); - if (!uncompressed.fail ()) - return m_Book.LoadHostsFromStream (uncompressed); - else + inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); + if (out.fail()) { + LogPrint(eLogError, "Addressbook: can't gunzip http response"); return false; - } - else - return m_Book.LoadHostsFromStream (s); + } + response = out.str(); + } + std::stringstream ss(response); + LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); + m_Book.LoadHostsFromStream (ss, true); + return true; } AddressResolver::AddressResolver (std::shared_ptr destination): @@ -821,7 +809,7 @@ namespace client { if (len < 9 || len < buf[8] + 9U) { - LogPrint (eLogError, "AddressBook: Address request is too short ", len); + LogPrint (eLogError, "Addressbook: Address request is too short ", len); return; } // read requested address @@ -829,7 +817,7 @@ namespace client char address[255]; memcpy (address, buf + 9, l); address[l] = 0; - LogPrint (eLogDebug, "AddressBook: Address request ", address); + LogPrint (eLogDebug, "Addressbook: Address request ", address); // send response uint8_t response[44]; memset (response, 0, 4); // reserved diff --git a/AddressBook.h b/AddressBook.h index d67089fa..fd852907 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -70,7 +70,7 @@ namespace client void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (std::shared_ptr address); - bool LoadHostsFromStream (std::istream& f); + bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } @@ -112,17 +112,17 @@ namespace client public: AddressBookSubscription (AddressBook& book, const std::string& link); - void CheckSubscription (); + void CheckUpdates (); private: - void Request (); - bool ProcessResponse (std::stringstream& s, bool isGzip = false); + bool MakeRequest (); private: AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; + i2p::data::IdentHash m_Ident; // m_Etag must be surrounded by "" }; diff --git a/BOB.cpp b/BOB.cpp index c074b53b..aa11c7b8 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -202,8 +202,8 @@ namespace client } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): - m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()), - m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), + m_Owner (owner), m_Socket (m_Owner.GetService ()), + m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } @@ -354,6 +354,11 @@ namespace client void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); + if (m_IsActive) + { + SendReplyError ("tunnel is active"); + return; + } if (!m_CurrentDestination) { m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); @@ -364,41 +369,27 @@ namespace client if (m_OutPort && !m_Address.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); - if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) - SendReplyOK ("tunnel starting"); - else - { - m_Timer.expires_from_now (boost::posix_time::seconds(BOB_SESSION_READINESS_CHECK_INTERVAL)); - m_Timer.async_wait (std::bind (&BOBCommandSession::HandleSessionReadinessCheckTimer, - shared_from_this (), std::placeholders::_1)); - } + SendReplyOK ("Tunnel starting"); + m_IsActive = true; } - - void BOBCommandSession::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) - SendReplyOK ("tunnel starting"); - else - { - m_Timer.expires_from_now (boost::posix_time::seconds(BOB_SESSION_READINESS_CHECK_INTERVAL)); - m_Timer.async_wait (std::bind (&BOBCommandSession::HandleSessionReadinessCheckTimer, - shared_from_this (), std::placeholders::_1)); - } - } - } void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { + LogPrint (eLogDebug, "BOB: stop ", m_Nickname); + if (!m_IsActive) + { + SendReplyError ("tunnel is inactive"); + return; + } auto dest = m_Owner.FindDestination (m_Nickname); if (dest) { dest->StopTunnels (); - SendReplyOK ("tunnel stopping"); + SendReplyOK ("Tunnel stopping"); } else SendReplyError ("tunnel not found"); + m_IsActive = false; } void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) @@ -406,7 +397,7 @@ namespace client LogPrint (eLogDebug, "BOB: setnick ", operand); m_Nickname = operand; std::string msg ("Nickname set to "); - msg += operand; + msg += m_Nickname; SendReplyOK (msg.c_str ()); } @@ -418,12 +409,15 @@ namespace client { m_Keys = m_CurrentDestination->GetKeys (); m_Nickname = operand; + } + if (m_Nickname == operand) + { std::string msg ("Nickname set to "); - msg += operand; + msg += m_Nickname; SendReplyOK (msg.c_str ()); - } + } else - SendReplyError ("tunnel not found"); + SendReplyError ("no nickname has been set"); } void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) @@ -463,7 +457,10 @@ namespace client { LogPrint (eLogDebug, "BOB: outport ", operand); m_OutPort = boost::lexical_cast(operand); - SendReplyOK ("outbound port set"); + if (m_OutPort >= 0) + SendReplyOK ("outbound port set"); + else + SendReplyError ("port out of range"); } void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) @@ -477,14 +474,27 @@ namespace client { LogPrint (eLogDebug, "BOB: inport ", operand); m_InPort = boost::lexical_cast(operand); - SendReplyOK ("inbound port set"); + if (m_InPort >= 0) + SendReplyOK ("inbound port set"); + else + SendReplyError ("port out of range"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quiet"); - m_IsQuiet = true; - SendReplyOK ("quiet"); + if (m_Nickname.length () > 0) + { + if (!m_IsActive) + { + m_IsQuiet = true; + SendReplyOK ("Quiet set"); + } + else + SendReplyError ("tunnel is active"); + } + else + SendReplyError ("no nickname has been set"); } void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) @@ -519,6 +529,7 @@ namespace client { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); + m_Nickname = ""; SendReplyOK ("cleared"); } @@ -537,14 +548,46 @@ namespace client const char * value = strchr (operand, '='); if (value) { + std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; + msg += operand; *(const_cast(value)) = '='; - SendReplyOK ("option"); + msg += " set to "; + msg += value; + SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); } + + void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) + { + LogPrint (eLogDebug, "BOB: status ", operand); + if (m_Nickname == operand) + { + std::stringstream s; + s << "DATA"; s << " NICKNAME:"; s << m_Nickname; + if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) + s << " STARTING:false RUNNING:true STOPPING:false"; + else + s << " STARTING:true RUNNING:false STOPPING:false"; + s << " KEYS: true"; s << " QUIET:"; s << (m_IsQuiet ? "true":"false"); + if (m_InPort) + { + s << " INPORT:" << m_InPort; + s << " INHOST:" << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + } + if (m_OutPort) + { + s << " OUTPORT:" << m_OutPort; + s << " OUTHOST:" << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); + } + SendReplyOK (s.str().c_str()); + } + else + SendReplyError ("no nickname has been set"); + } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), @@ -570,6 +613,7 @@ namespace client m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; + m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; } BOBCommandChannel::~BOBCommandChannel () diff --git a/BOB.h b/BOB.h index a6a6fa11..104073bd 100644 --- a/BOB.h +++ b/BOB.h @@ -36,14 +36,13 @@ namespace client const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; + const char BOB_COMMAND_STATUS[] = "status"; const char BOB_VERSION[] = "BOB 00.00.10\nOK\n"; const char BOB_REPLY_OK[] = "OK %s\n"; const char BOB_REPLY_ERROR[] = "ERROR %s\n"; const char BOB_DATA[] = "NICKNAME %s\n"; - const int BOB_SESSION_READINESS_CHECK_INTERVAL = 5; // in seconds - class BOBI2PTunnel: public I2PService { public: @@ -170,12 +169,12 @@ namespace client void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); + void StatusCommandHandler (const char * operand, size_t len); private: void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void Send (size_t len); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); @@ -187,10 +186,9 @@ namespace client BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; - boost::asio::deadline_timer m_Timer; char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1]; size_t m_ReceiveBufferOffset; - bool m_IsOpen, m_IsQuiet; + bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_Address; int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; diff --git a/Config.cpp b/Config.cpp index 10330c34..2a35ce7e 100644 --- a/Config.cpp +++ b/Config.cpp @@ -128,12 +128,13 @@ namespace config { ; bool upnp_default = false; -#if (defined(USE_UPNP) && ((defined(WIN32) && defined(USE_WIN32_APP)) || defined(ANDROID))) +#if (defined(USE_UPNP) && (defined(WIN32_APP) || defined(ANDROID))) upnp_default = true; // enable UPNP for windows GUI and android by default #endif options_description upnp("UPnP options"); upnp.add_options() ("upnp.enabled", value()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding") + ("upnp.name", value()->default_value("I2Pd"), "Name i2pd appears in UPnP forwardings list") ; options_description precomputation("Precomputation options"); diff --git a/HTTP.cpp b/HTTP.cpp index 20f07448..4a0286a7 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -279,10 +279,7 @@ namespace http { } bool HTTPRes::is_gzipped() { - auto it = headers.find("x-i2p-gzip"); - if (it == headers.end()) - return true; /* i2p-specific header */ - it = headers.find("Content-Encoding"); + auto it = headers.find("Content-Encoding"); if (it == headers.end()) return false; /* no header */ if (it->second.find("gzip") != std::string::npos) diff --git a/Reseed.cpp b/Reseed.cpp index 096035da..236531f9 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -14,6 +14,7 @@ #include "Log.h" #include "Identity.h" #include "NetDb.h" +#include "HTTP.h" #include "util.h" namespace i2p @@ -372,13 +373,19 @@ namespace data std::string Reseeder::HttpsRequest (const std::string& address) { - i2p::util::http::url u(address); - if (u.port_ == 80) u.port_ = 443; + i2p::http::URL url; + if (!url.parse(address)) { + LogPrint(eLogError, "Reseed: failed to parse url: ", address); + return ""; + } + url.schema = "https"; + if (!url.port) + url.port = 443; boost::asio::io_service service; boost::system::error_code ecode; - auto it = boost::asio::ip::tcp::resolver(service).resolve ( - boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode); + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); if (!ecode) { boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); @@ -390,32 +397,52 @@ namespace data s.handshake (boost::asio::ssl::stream_base::client, ecode); if (!ecode) { - LogPrint (eLogInfo, "Reseed: Connected to ", u.host_, ":", u.port_); - // send request - std::stringstream ss; - ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_ - << "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n"; - s.write_some (boost::asio::buffer (ss.str ())); + LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); + i2p::http::HTTPReq req; + req.uri = url.to_string(); + req.add_header("User-Agent", "Wget/1.11.4"); + req.add_header("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); // read response std::stringstream rs; - char response[1024]; size_t l = 0; - do - { - l = s.read_some (boost::asio::buffer (response, 1024), ecode); - if (l) rs.write (response, l); - } - while (!ecode && l); + char recv_buf[1024]; size_t l = 0; + do { + l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); + if (l) rs.write (recv_buf, l); + } while (!ecode && l); // process response - return i2p::util::http::GetHttpContent (rs); + std::string data = rs.str(); + i2p::http::HTTPRes res; + int len = res.parse(data); + if (len <= 0) { + LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host); + return ""; + } + if (res.code != 200) { + LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code); + return ""; + } + data.erase(0, len); /* drop http headers from response */ + LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host); + if (res.is_chunked()) { + std::stringstream in(data), out; + if (!i2p::http::MergeChunkedResponse(in, out)) { + LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host); + return ""; + } + LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host); + data = out.str(); + } + return data; } else LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); } else - LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); } else - LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ()); return ""; } } diff --git a/RouterContext.cpp b/RouterContext.cpp index b2d4f073..737a92fc 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -51,7 +51,16 @@ namespace i2p port = rand () % (30777 - 9111) + 9111; // I2P network ports range bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); - std::string host = i2p::util::config::GetHost(ipv4, ipv6); + bool nat; i2p::config::GetOption("nat", nat); + std::string ifname; i2p::config::GetOption("ifname", ifname); + std::string host = ipv6 ? "::" : "127.0.0.1"; + if (nat) { + if (!i2p::config::IsDefault("host")) + i2p::config::GetOption("host", host); + } else if (!ifname.empty()) { + /* bind to interface, we have no NAT so set external address too */ + host = i2p::util::net::GetInterfaceAddress(ifname, ipv6).to_string(); + } routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | @@ -224,11 +233,12 @@ namespace i2p m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B // remove NTCP address auto& addresses = m_RouterInfo.GetAddresses (); - for (size_t i = 0; i < addresses.size (); i++) + for (auto it = addresses.begin (); it != addresses.end (); it++) { - if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP) + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*it)->host.is_v4 ()) { - addresses.erase (addresses.begin () + i); + addresses.erase (it); break; } } @@ -253,12 +263,13 @@ namespace i2p // insert NTCP back auto& addresses = m_RouterInfo.GetAddresses (); - for (size_t i = 0; i < addresses.size (); i++) + for (auto addr : addresses) { - if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU) + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && + addr->host.is_v4 ()) { // insert NTCP address with host/port from SSU - m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port); + m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); break; } } diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 8e9f8865..f497c30e 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -700,28 +700,14 @@ namespace data { if (IsV6 ()) { - // NTCP - m_SupportedTransports &= ~eNTCPV6; - for (size_t i = 0; i < m_Addresses->size (); i++) + m_SupportedTransports &= ~(eNTCPV6 | eSSUV6); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::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 && - (*m_Addresses)[i]->host.is_v6 ()) - { - m_Addresses->erase (m_Addresses->begin () + i); - break; - } + auto addr = *it; + if (addr->host.is_v6 ()) + it = m_Addresses->erase (it); + else + it++; } } } @@ -730,28 +716,14 @@ namespace data { if (IsV4 ()) { - // NTCP - m_SupportedTransports &= ~eNTCPV4; - for (size_t i = 0; i < m_Addresses->size (); i++) + m_SupportedTransports &= ~(eNTCPV4 | eSSUV4); + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { - if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::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 && - (*m_Addresses)[i]->host.is_v4 ()) - { - m_Addresses->erase (m_Addresses->begin () + i); - break; - } + auto addr = *it; + if (addr->host.is_v4 ()) + it = m_Addresses->erase (it); + else + it++; } } } diff --git a/RouterInfo.h b/RouterInfo.h index a23c32e3..8c99d245 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "Identity.h" @@ -105,7 +106,7 @@ namespace data return !(*this == other); } }; - typedef std::vector > Addresses; + typedef std::list > Addresses; RouterInfo (); RouterInfo (const std::string& fullPath); diff --git a/UPnP.cpp b/UPnP.cpp index fbb8639b..41b49e00 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -13,6 +13,7 @@ #include "NetDb.h" #include "util.h" #include "RouterInfo.h" +#include "Config.h" #include #include @@ -21,49 +22,54 @@ namespace i2p { namespace transport { - UPnP::UPnP () : m_Thread (nullptr) + UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) { } void UPnP::Stop () { - LogPrint(eLogInfo, "UPnP: stopping"); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } + if (m_IsRunning) + { + LogPrint(eLogInfo, "UPnP: stopping"); + m_IsRunning = false; + m_Timer.cancel (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + m_Thread.reset (nullptr); + } + CloseMapping (); + Close (); + } } void UPnP::Start() { + m_IsRunning = true; LogPrint(eLogInfo, "UPnP: starting"); - m_Thread = new std::thread (std::bind (&UPnP::Run, this)); + m_Service.post (std::bind (&UPnP::Discover, this)); + m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); } UPnP::~UPnP () { + Stop (); } void UPnP::Run () { - const std::vector > a = context.GetRouterInfo().GetAddresses(); - for (auto address : a) - { - if (!address->host.is_v6 ()) - { - Discover (); - if (address->transportStyle == data::RouterInfo::eTransportSSU ) - { - TryPortMapping (I2P_UPNP_UDP, address->port); - } - else if (address->transportStyle == data::RouterInfo::eTransportNTCP ) - { - TryPortMapping (I2P_UPNP_TCP, address->port); - } - } - } + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); + } + } } void UPnP::Discover () @@ -87,73 +93,74 @@ namespace transport } else { - if (m_externalIPAddress[0]) - { - LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); - return; - } - else + if (!m_externalIPAddress[0]) { LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed."); return; } } } + else + { + LogPrint (eLogError, "UPnP: GetValidIGD() failed."); + return; + } + + // UPnP discovered + LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); + i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); + // port mapping + PortMapping (); } - void UPnP::TryPortMapping (int type, int port) - { - std::string strType, strPort (std::to_string (port)); - switch (type) + void UPnP::PortMapping () + { + auto a = context.GetRouterInfo().GetAddresses(); + for (auto address : a) { - case I2P_UPNP_TCP: - strType = "TCP"; - break; - case I2P_UPNP_UDP: - default: - strType = "UDP"; + if (!address->host.is_v6 ()) + TryPortMapping (address); + } + m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes + m_Timer.async_wait ([this](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + PortMapping (); + }); + + } + + void UPnP::CloseMapping () + { + auto a = context.GetRouterInfo().GetAddresses(); + for (auto address : a) + { + if (!address->host.is_v6 ()) + CloseMapping (address); } + } + + void UPnP::TryPortMapping (std::shared_ptr address) + { + std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r; - std::string strDesc = "I2Pd"; - try { - for (;;) { - r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); - if (r!=UPNPCOMMAND_SUCCESS) - { - LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") failed with code ", r); - return; - } - else - { - LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")"); - return; - } - std::this_thread::sleep_for(std::chrono::minutes(20)); // c++11 - //boost::this_thread::sleep_for(); // pre c++11 - //sleep(20*60); // non-portable - } + std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); + r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); + if (r!=UPNPCOMMAND_SUCCESS) + { + LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") failed with code ", r); + return; } - catch (boost::thread_interrupted) + else { - CloseMapping(type, port); - Close(); - throw; + LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")"); + return; } } - void UPnP::CloseMapping (int type, int port) + void UPnP::CloseMapping (std::shared_ptr address) { - std::string strType, strPort (std::to_string (port)); - switch (type) - { - case I2P_UPNP_TCP: - strType = "TCP"; - break; - case I2P_UPNP_UDP: - default: - strType = "UDP"; - } + std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r = 0; r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r); @@ -165,6 +172,19 @@ namespace transport m_Devlist = 0; FreeUPNPUrls (&m_upnpUrls); } + + std::string UPnP::GetProto (std::shared_ptr address) + { + switch (address->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP: + return "TCP"; + break; + case i2p::data::RouterInfo::eTransportSSU: + default: + return "UDP"; + } + } } } #else /* USE_UPNP */ diff --git a/UPnP.h b/UPnP.h index 2831989a..4013b3df 100644 --- a/UPnP.h +++ b/UPnP.h @@ -4,6 +4,7 @@ #ifdef USE_UPNP #include #include +#include #include #include @@ -14,9 +15,6 @@ #include "util.h" -#define I2P_UPNP_TCP 1 -#define I2P_UPNP_UDP 2 - namespace i2p { namespace transport @@ -32,13 +30,23 @@ namespace transport void Start (); void Stop (); - void Discover (); - void TryPortMapping (int type, int port); - void CloseMapping (int type, int port); private: + + void Discover (); + void PortMapping (); + void TryPortMapping (std::shared_ptr address); + void CloseMapping (); + void CloseMapping (std::shared_ptr address); + void Run (); + std::string GetProto (std::shared_ptr address); - std::thread * m_Thread; + private: + + bool m_IsRunning; + std::unique_ptr m_Thread; + boost::asio::io_service m_Service; + boost::asio::deadline_timer m_Timer; struct UPNPUrls m_upnpUrls; struct IGDdatas m_upnpData; diff --git a/docs/configuration.md b/docs/configuration.md index 9a5d1826..e6ac74d2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,8 +68,8 @@ All options below still possible in cmdline, but better write it in config file: * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified * --i2pcontrol.enabled= - If I2P control is enabled. false by default -* --upnp.enabled= - Enable or disable UPnP, false by default for CLI and true for GUI (Windows, Android) - +* --upnp.enabled= - Enable or disable UPnP, false by default for CLI and true for GUI (Windows, Android) +* --upnp.name= - Name i2pd appears in UPnP forwardings list. I2Pd by default * --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default diff --git a/util.cpp b/util.cpp index 5913d9a7..08ee6672 100644 --- a/util.cpp +++ b/util.cpp @@ -7,7 +7,6 @@ #include #include #include -#include "Config.h" #include "util.h" #include "Log.h" @@ -460,31 +459,5 @@ namespace net #endif } } - -namespace config -{ - std::string GetHost(bool ipv4, bool ipv6) - { - std::string host; - if(ipv6) - host = "::"; - else if(ipv4) - host = "127.0.0.1"; - bool nat; i2p::config::GetOption("nat", nat); - if (nat) - { - if (!i2p::config::IsDefault("host")) - i2p::config::GetOption("host", host); - } - else - { - // we are not behind nat - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ifname.size()) - host = i2p::util::net::GetInterfaceAddress(ifname, ipv6).to_string(); // bind to interface, we have no NAT so set external address too - } - return host; - } -} // config } // util } // i2p diff --git a/util.h b/util.h index 8995f12b..642ecc9b 100644 --- a/util.h +++ b/util.h @@ -68,14 +68,7 @@ namespace util int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); } - - namespace config - { - /** get the host to use from out config, for use in RouterContext.cpp */ - std::string GetHost(bool ipv4=true, bool ipv6=true); - } } } - #endif